Merge "EditPage: Restore getSummaryInput(); deprecate it and getSummaryInputOOUI()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 1 Sep 2017 20:17:01 +0000 (20:17 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 1 Sep 2017 20:17:01 +0000 (20:17 +0000)
217 files changed:
RELEASE-NOTES-1.30
autoload.php
includes/Block.php
includes/CommentStore.php [new file with mode: 0644]
includes/CommentStoreComment.php [new file with mode: 0644]
includes/DefaultSettings.php
includes/EditPage.php
includes/FeedUtils.php
includes/Linker.php
includes/MagicWordArray.php
includes/PageProps.php
includes/Revision.php
includes/Title.php
includes/WatchedItemQueryService.php
includes/api/ApiMove.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryProtectedTitles.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/i18n/es.json
includes/api/i18n/eu.json
includes/api/i18n/lb.json
includes/cache/LinkBatch.php
includes/changes/ChangesListBooleanFilter.php
includes/changes/RecentChange.php
includes/config/EtcdConfig.php
includes/config/EtcdConfigParseError.php [new file with mode: 0644]
includes/db/DatabaseOracle.php
includes/deferred/CdnCacheUpdate.php
includes/export/WikiExporter.php
includes/export/XmlDumpWriter.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/OldLocalFile.php
includes/import/WikiImporter.php
includes/import/WikiRevision.php
includes/installer/DatabaseUpdater.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/da.json
includes/installer/i18n/es.json
includes/installer/i18n/eu.json
includes/libs/IP.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/utils/NextSequenceValue.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBConnectionError.php
includes/libs/rdbms/exception/DBError.php
includes/libs/rdbms/exception/DBExpectedError.php
includes/libs/rdbms/exception/DBQueryError.php
includes/libs/rdbms/exception/DBTransactionSizeError.php
includes/logging/LogEntry.php
includes/logging/LogPage.php
includes/page/PageArchive.php
includes/page/WikiPage.php
includes/parser/CoreParserFunctions.php
includes/parser/Parser.php
includes/rcfeed/IRCColourfulRCFeedFormatter.php
includes/revisiondelete/RevDelLogItem.php
includes/revisiondelete/RevDelLogList.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialNewpages.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialWatchlist.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/NewPagesPager.php
includes/specials/pagers/ProtectedPagesPager.php
includes/upload/UploadFromUrl.php
languages/i18n/ais.json [new file with mode: 0644]
languages/i18n/ar.json
languages/i18n/be-tarask.json
languages/i18n/br.json
languages/i18n/ce.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/da.json
languages/i18n/de-formal.json
languages/i18n/de.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gor.json
languages/i18n/gu.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hr.json
languages/i18n/hsb.json
languages/i18n/ia.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/kab.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/mwl.json
languages/i18n/my.json
languages/i18n/nb.json
languages/i18n/ne.json
languages/i18n/nn.json
languages/i18n/or.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/skr-arab.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/su.json
languages/i18n/sv.json
languages/i18n/tay.json
languages/i18n/ur.json
languages/i18n/vi.json
languages/i18n/zh-hans.json
maintenance/archives/patch-categorylinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/archives/patch-imagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-iwlinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-langlinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-log_search-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-log_search-rename-index.sql [deleted file]
maintenance/archives/patch-module_deps-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-objectcache-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-pagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-querycache_info-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-site_stats-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-templatelinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-text-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-transcache-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-user_former_groups-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-user_properties-fix-pk.sql [new file with mode: 0644]
maintenance/migrateComments.php [new file with mode: 0644]
maintenance/oracle/archives/patch-auto_increment_triggers.sql [new file with mode: 0644]
maintenance/oracle/tables.sql
maintenance/orphans.php
maintenance/postgres/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/postgres/tables.sql
maintenance/rebuildrecentchanges.php
maintenance/sqlite/archives/patch-categorylinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-imagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-iwlinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-langlinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-log_search-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-log_search-rename-index.sql [deleted file]
maintenance/sqlite/archives/patch-module_deps-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-objectcache-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-pagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-querycache_info-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-site_stats-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-templatelinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-text-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-transcache-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-user_former_groups-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-user_properties-fix-pk.sql [new file with mode: 0644]
maintenance/tables.sql
resources/Resources.php
resources/src/mediawiki.action/mediawiki.action.edit.styles.css [deleted file]
resources/src/mediawiki.action/mediawiki.action.edit.styles.less [new file with mode: 0644]
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.SavedQueriesModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.UriProcessor.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.mixins.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.RcTopSectionWidget.less [new file with mode: 0644]
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListItemWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.WatchlistTopSectionWidget.less [new file with mode: 0644]
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MarkSeenButtonWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.RcTopSectionWidget.js [new file with mode: 0644]
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.WatchlistTopSectionWidget.js [new file with mode: 0644]
resources/src/mediawiki.special/mediawiki.special.search.interwikiwidget.styles.less
resources/src/mediawiki.toolbar/images/ksh/LICENSE
tests/parser/ParserTestRunner.php
tests/parser/parserTests.txt
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/CommentStoreTest.php [new file with mode: 0644]
tests/phpunit/includes/RevisionStorageTest.php
tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
tests/phpunit/includes/changes/RecentChangeTest.php
tests/phpunit/includes/changes/TestRecentChangesHelper.php
tests/phpunit/includes/config/EtcdConfigTest.php
tests/phpunit/includes/deferred/LinksUpdateTest.php
tests/phpunit/includes/libs/IPTest.php
tests/phpunit/includes/logging/LogFormatterTestCase.php
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/suites/ParserTestTopLevelSuite.php
tests/selenium/wdio.conf.js

index 1075999..93d52b8 100644 (file)
@@ -61,6 +61,11 @@ section).
   just been unwatched.
 * Added $wgParserTestMediaHandlers, where mock media handlers can be passed to
   MediaHandlerFactory for parser tests.
+* Edit summaries, block reasons, and other "comments" are now stored in a
+  separate database table. Use the CommentFormatter class to access them.
+** This is currently gated by $wgCommentTableSchemaMigrationStage. Most wikis
+   can set this to MIGRATION_NEW and run maintenance/migrateComments.php as
+   soon as any necessary extensions are updated.
 
 === External library changes in 1.30 ===
 
@@ -152,6 +157,7 @@ changes to languages because of Phabricator reports.
   WikiPage::makeParserOptions() to create the ParserOptions object and only
   change options that affect the parser cache key.
 * Article::viewRedirect() is deprecated.
+* IP::isValidBlock() was deprecated. Use the equivalent IP::isValidRange().
 * DeprecatedGlobal no longer supports passing in a direct value, it requires a
   callable factory function or a class name.
 * The $parserMemc global, wfGetParserCacheStorage(), and ParserCache::singleton()
@@ -187,8 +193,14 @@ changes to languages because of Phabricator reports.
   use ::getSummaryInputWidget() instead.
 * Parser::getRandomString() (deprecated in 1.26) was removed.
 * Parser::uniqPrefix() (deprecated in 1.26) was removed.
-* Parser::extractTagsAndParams() now only accepts three arguments.  The fourth,
+* Parser::extractTagsAndParams() now only accepts three arguments. The fourth,
   $uniq_prefix was deprecated in 1.26 and has now been removed.
+* (T172514) The following tables have had their UNIQUE indexes turned into proper
+  PRIMARY KEYs for increased maintainability: categorylinks, imagelinks, iwlinks,
+  langlinks, log_search, module_deps, objectcache, pagelinks, query_cache, site_stats,
+  templatelinks, text, transcache, user_former_groups, user_properties.
+* IDatabase::nextSequenceValue() is no longer needed by any database backends
+  (formerly it was needed by PostgreSQL and Oracle), and is now deprecated.
 
 == Compatibility ==
 MediaWiki 1.30 requires PHP 5.5.9 or later. There is experimental support for
index 8062387..eab8e45 100644 (file)
@@ -276,6 +276,8 @@ $wgAutoloadLocalClasses = [
        'CollationFa' => __DIR__ . '/includes/collation/CollationFa.php',
        'CommandLineInc' => __DIR__ . '/maintenance/commandLine.inc',
        'CommandLineInstaller' => __DIR__ . '/maintenance/install.php',
+       'CommentStore' => __DIR__ . '/includes/CommentStore.php',
+       'CommentStoreComment' => __DIR__ . '/includes/CommentStoreComment.php',
        'CompareParserCache' => __DIR__ . '/maintenance/compareParserCache.php',
        'CompareParsers' => __DIR__ . '/maintenance/compareParsers.php',
        'ComposerHookHandler' => __DIR__ . '/includes/composer/ComposerHookHandler.php',
@@ -435,6 +437,7 @@ $wgAutoloadLocalClasses = [
        'EraseArchivedFile' => __DIR__ . '/maintenance/eraseArchivedFile.php',
        'ErrorPageError' => __DIR__ . '/includes/exception/ErrorPageError.php',
        'EtcdConfig' => __DIR__ . '/includes/config/EtcdConfig.php',
+       'EtcdConfigParseError' => __DIR__ . '/includes/config/EtcdConfigParseError.php',
        'EventRelayer' => __DIR__ . '/includes/libs/eventrelayer/EventRelayer.php',
        'EventRelayerGroup' => __DIR__ . '/includes/EventRelayerGroup.php',
        'EventRelayerKafka' => __DIR__ . '/includes/libs/eventrelayer/EventRelayerKafka.php',
@@ -982,6 +985,7 @@ $wgAutoloadLocalClasses = [
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
        'MessageLocalizer' => __DIR__ . '/languages/MessageLocalizer.php',
        'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
+       'MigrateComments' => __DIR__ . '/maintenance/migrateComments.php',
        'MigrateFileRepoLayout' => __DIR__ . '/maintenance/migrateFileRepoLayout.php',
        'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php',
        'MimeAnalyzer' => __DIR__ . '/includes/libs/mime/MimeAnalyzer.php',
@@ -1663,6 +1667,7 @@ $wgAutoloadLocalClasses = [
        'Wikimedia\\Rdbms\\MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php',
        'Wikimedia\\Rdbms\\MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php',
        'Wikimedia\\Rdbms\\MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
+       'Wikimedia\\Rdbms\\NextSequenceValue' => __DIR__ . '/includes/libs/rdbms/database/utils/NextSequenceValue.php',
        'Wikimedia\\Rdbms\\PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
        'Wikimedia\\Rdbms\\PostgresField' => __DIR__ . '/includes/libs/rdbms/field/PostgresField.php',
        'Wikimedia\\Rdbms\\ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php',
index 5066038..0b17e93 100644 (file)
@@ -199,6 +199,8 @@ class Block {
        /**
         * Return the list of ipblocks fields that should be selected to create
         * a new block.
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        public static function selectFields() {
@@ -207,7 +209,6 @@ class Block {
                        'ipb_address',
                        'ipb_by',
                        'ipb_by_text',
-                       'ipb_reason',
                        'ipb_timestamp',
                        'ipb_auto',
                        'ipb_anon_only',
@@ -218,7 +219,7 @@ class Block {
                        'ipb_block_email',
                        'ipb_allow_usertalk',
                        'ipb_parent_block_id',
-               ];
+               ] + CommentStore::newKey( 'ipb_reason' )->getFields();
        }
 
        /**
@@ -411,7 +412,6 @@ class Block {
                        $this->setBlocker( $row->ipb_by_text );
                }
 
-               $this->mReason = $row->ipb_reason;
                $this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
                $this->mAuto = $row->ipb_auto;
                $this->mHideName = $row->ipb_deleted;
@@ -419,7 +419,11 @@ class Block {
                $this->mParentBlockId = $row->ipb_parent_block_id;
 
                // I wish I didn't have to do this
-               $this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $row->ipb_expiry );
+               $db = wfGetDB( DB_REPLICA );
+               $this->mExpiry = $db->decodeExpiry( $row->ipb_expiry );
+               $this->mReason = CommentStore::newKey( 'ipb_reason' )
+                       // Legacy because $row probably came from self::selectFields()
+                       ->getCommentLegacy( $db, $row )->text;
 
                $this->isHardblock( !$row->ipb_anon_only );
                $this->isAutoblocking( $row->ipb_enable_autoblock );
@@ -488,7 +492,7 @@ class Block {
                        self::purgeExpired();
                }
 
-               $row = $this->getDatabaseArray();
+               $row = $this->getDatabaseArray( $dbw );
                $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" );
 
                $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
@@ -558,7 +562,7 @@ class Block {
                        // update corresponding autoblock(s) (T50813)
                        $dbw->update(
                                'ipblocks',
-                               $this->getAutoblockUpdateArray(),
+                               $this->getAutoblockUpdateArray( $dbw ),
                                [ 'ipb_parent_block_id' => $this->getId() ],
                                __METHOD__
                        );
@@ -583,14 +587,11 @@ class Block {
 
        /**
         * Get an array suitable for passing to $dbw->insert() or $dbw->update()
-        * @param IDatabase $db
+        * @param IDatabase $dbw
         * @return array
         */
-       protected function getDatabaseArray( $db = null ) {
-               if ( !$db ) {
-                       $db = wfGetDB( DB_REPLICA );
-               }
-               $expiry = $db->encodeExpiry( $this->mExpiry );
+       protected function getDatabaseArray( IDatabase $dbw ) {
+               $expiry = $dbw->encodeExpiry( $this->mExpiry );
 
                if ( $this->forcedTargetID ) {
                        $uid = $this->forcedTargetID;
@@ -603,8 +604,7 @@ class Block {
                        'ipb_user'             => $uid,
                        'ipb_by'               => $this->getBy(),
                        'ipb_by_text'          => $this->getByName(),
-                       'ipb_reason'           => $this->mReason,
-                       'ipb_timestamp'        => $db->timestamp( $this->mTimestamp ),
+                       'ipb_timestamp'        => $dbw->timestamp( $this->mTimestamp ),
                        'ipb_auto'             => $this->mAuto,
                        'ipb_anon_only'        => !$this->isHardblock(),
                        'ipb_create_account'   => $this->prevents( 'createaccount' ),
@@ -616,23 +616,23 @@ class Block {
                        'ipb_block_email'      => $this->prevents( 'sendemail' ),
                        'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
                        'ipb_parent_block_id'  => $this->mParentBlockId
-               ];
+               ] + CommentStore::newKey( 'ipb_reason' )->insert( $dbw, $this->mReason );
 
                return $a;
        }
 
        /**
+        * @param IDatabase $dbw
         * @return array
         */
-       protected function getAutoblockUpdateArray() {
+       protected function getAutoblockUpdateArray( IDatabase $dbw ) {
                return [
                        'ipb_by'               => $this->getBy(),
                        'ipb_by_text'          => $this->getByName(),
-                       'ipb_reason'           => $this->mReason,
                        'ipb_create_account'   => $this->prevents( 'createaccount' ),
                        'ipb_deleted'          => (int)$this->mHideName, // typecast required for SQLite
                        'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
-               ];
+               ] + CommentStore::newKey( 'ipb_reason' )->insert( $dbw, $this->mReason );
        }
 
        /**
@@ -1354,7 +1354,7 @@ class Block {
                                self::TYPE_IP
                        ];
 
-               } elseif ( IP::isValidBlock( $target ) ) {
+               } elseif ( IP::isValidRange( $target ) ) {
                        # Can't create a User from an IP range
                        return [ IP::sanitizeRange( $target ), self::TYPE_RANGE ];
                }
diff --git a/includes/CommentStore.php b/includes/CommentStore.php
new file mode 100644 (file)
index 0000000..0c86c1e
--- /dev/null
@@ -0,0 +1,567 @@
+<?php
+/**
+ * Manage storage of comments in the database
+ *
+ * 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 Wikimedia\Rdbms\IDatabase;
+
+/**
+ * CommentStore handles storage of comments (edit summaries, log reasons, etc)
+ * in the database.
+ * @since 1.30
+ */
+class CommentStore {
+
+       /**
+        * Define fields that use temporary tables for transitional purposes
+        * @var array Keys are '$key', values are arrays with four fields:
+        *  - table: Temporary table name
+        *  - pk: Temporary table column referring to the main table's primary key
+        *  - field: Temporary table column referring comment.comment_id
+        *  - joinPK: Main table's primary key
+        */
+       protected static $tempTables = [
+               'rev_comment' => [
+                       'table' => 'revision_comment_temp',
+                       'pk' => 'revcomment_rev',
+                       'field' => 'revcomment_comment_id',
+                       'joinPK' => 'rev_id',
+               ],
+               'img_description' => [
+                       'table' => 'image_comment_temp',
+                       'pk' => 'imgcomment_name',
+                       'field' => 'imgcomment_description_id',
+                       'joinPK' => 'img_name',
+               ],
+       ];
+
+       /**
+        * Fields that formerly used $tempTables
+        * @var array Key is '$key', value is the MediaWiki version in which it was
+        *  removed from $tempTables.
+        */
+       protected static $formerTempTables = [];
+
+       /** @var string */
+       protected $key;
+
+       /** @var int One of the MIGRATION_* constants */
+       protected $stage;
+
+       /** @var array|null Cache for `self::getJoin()` */
+       protected $joinCache = null;
+
+       /**
+        * @param string $key A key such as "rev_comment" identifying the comment
+        *  field being fetched.
+        */
+       public function __construct( $key ) {
+               global $wgCommentTableSchemaMigrationStage;
+
+               $this->key = $key;
+               $this->stage = $wgCommentTableSchemaMigrationStage;
+       }
+
+       /**
+        * Static constructor for easier chaining
+        * @param string $key A key such as "rev_comment" identifying the comment
+        *  field being fetched.
+        * @return CommentStore
+        */
+       public static function newKey( $key ) {
+               return new CommentStore( $key );
+       }
+
+       /**
+        * Get SELECT fields for the comment key
+        *
+        * Each resulting row should be passed to `self::getCommentLegacy()` to get the
+        * actual comment.
+        *
+        * @note Use of this method may require a subsequent database query to
+        *  actually fetch the comment. If possible, use `self::getJoin()` instead.
+        * @return string[] to include in the `$vars` to `IDatabase->select()`. All
+        *  fields are aliased, so `+` is safe to use.
+        */
+       public function getFields() {
+               $fields = [];
+               if ( $this->stage === MIGRATION_OLD ) {
+                       $fields["{$this->key}_text"] = $this->key;
+                       $fields["{$this->key}_data"] = 'NULL';
+                       $fields["{$this->key}_cid"] = 'NULL';
+               } else {
+                       if ( $this->stage < MIGRATION_NEW ) {
+                               $fields["{$this->key}_old"] = $this->key;
+                       }
+                       if ( isset( self::$tempTables[$this->key] ) ) {
+                               $fields["{$this->key}_pk"] = self::$tempTables[$this->key]['joinPK'];
+                       } else {
+                               $fields["{$this->key}_id"] = "{$this->key}_id";
+                       }
+               }
+               return $fields;
+       }
+
+       /**
+        * Get SELECT fields and joins for the comment key
+        *
+        * Each resulting row should be passed to `self::getComment()` to get the
+        * actual comment.
+        *
+        * @return array With three keys:
+        *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
+        *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
+        *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
+        *  All tables, fields, and joins are aliased, so `+` is safe to use.
+        */
+       public function getJoin() {
+               if ( $this->joinCache === null ) {
+                       $tables = [];
+                       $fields = [];
+                       $joins = [];
+
+                       if ( $this->stage === MIGRATION_OLD ) {
+                               $fields["{$this->key}_text"] = $this->key;
+                               $fields["{$this->key}_data"] = 'NULL';
+                               $fields["{$this->key}_cid"] = 'NULL';
+                       } else {
+                               $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
+
+                               if ( isset( self::$tempTables[$this->key] ) ) {
+                                       $t = self::$tempTables[$this->key];
+                                       $alias = "temp_$this->key";
+                                       $tables[$alias] = $t['table'];
+                                       $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
+                                       $joinField = "{$alias}.{$t['field']}";
+                               } else {
+                                       $joinField = "{$this->key}_id";
+                               }
+
+                               $alias = "comment_$this->key";
+                               $tables[$alias] = 'comment';
+                               $joins[$alias] = [ $join, "{$alias}.comment_id = {$joinField}" ];
+
+                               if ( $this->stage === MIGRATION_NEW ) {
+                                       $fields["{$this->key}_text"] = "{$alias}.comment_text";
+                               } else {
+                                       $fields["{$this->key}_text"] = "COALESCE( {$alias}.comment_text, $this->key )";
+                               }
+                               $fields["{$this->key}_data"] = "{$alias}.comment_data";
+                               $fields["{$this->key}_cid"] = "{$alias}.comment_id";
+                       }
+
+                       $this->joinCache = [
+                               'tables' => $tables,
+                               'fields' => $fields,
+                               'joins' => $joins,
+                       ];
+               }
+
+               return $this->joinCache;
+       }
+
+       /**
+        * Extract the comment from a row
+        *
+        * Shared implementation for getComment() and getCommentLegacy()
+        *
+        * @param IDatabase|null $db Database handle for getCommentLegacy(), or null for getComment()
+        * @param object|array $row
+        * @param bool $fallback
+        * @return CommentStoreComment
+        */
+       private function getCommentInternal( IDatabase $db = null, $row, $fallback = false ) {
+               $key = $this->key;
+               $row = (array)$row;
+               if ( array_key_exists( "{$key}_text", $row ) && array_key_exists( "{$key}_data", $row ) ) {
+                       $cid = isset( $row["{$key}_cid"] ) ? $row["{$key}_cid"] : null;
+                       $text = $row["{$key}_text"];
+                       $data = $row["{$key}_data"];
+               } elseif ( $this->stage === MIGRATION_OLD ) {
+                       $cid = null;
+                       if ( $fallback && isset( $row[$key] ) ) {
+                               wfLogWarning( "Using deprecated fallback handling for comment $key" );
+                               $text = $row[$key];
+                       } else {
+                               wfLogWarning( "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD" );
+                               $text = '';
+                       }
+                       $data = null;
+               } else {
+                       if ( isset( self::$tempTables[$key] ) ) {
+                               if ( array_key_exists( "{$key}_pk", $row ) ) {
+                                       if ( !$db ) {
+                                               throw new InvalidArgumentException(
+                                                       "\$row does not contain fields needed for comment $key and getComment(), but "
+                                                       . "does have fields for getCommentLegacy()"
+                                               );
+                                       }
+                                       $t = self::$tempTables[$key];
+                                       $id = $row["{$key}_pk"];
+                                       $row2 = $db->selectRow(
+                                               [ $t['table'], 'comment' ],
+                                               [ 'comment_id', 'comment_text', 'comment_data' ],
+                                               [ $t['pk'] => $id ],
+                                               __METHOD__,
+                                               [],
+                                               [ 'comment' => [ 'JOIN', [ "comment_id = {$t['field']}" ] ] ]
+                                       );
+                               } elseif ( $fallback && isset( $row[$key] ) ) {
+                                       wfLogWarning( "Using deprecated fallback handling for comment $key" );
+                                       $row2 = (object)[ 'comment_text' => $row[$key], 'comment_data' => null ];
+                               } else {
+                                       throw new InvalidArgumentException( "\$row does not contain fields needed for comment $key" );
+                               }
+                       } else {
+                               if ( array_key_exists( "{$key}_id", $row ) ) {
+                                       if ( !$db ) {
+                                               throw new InvalidArgumentException(
+                                                       "\$row does not contain fields needed for comment $key and getComment(), but "
+                                                       . "does have fields for getCommentLegacy()"
+                                               );
+                                       }
+                                       $id = $row["{$key}_id"];
+                                       $row2 = $db->selectRow(
+                                               'comment',
+                                               [ 'comment_id', 'comment_text', 'comment_data' ],
+                                               [ 'comment_id' => $id ],
+                                               __METHOD__
+                                       );
+                               } elseif ( $fallback && isset( $row[$key] ) ) {
+                                       wfLogWarning( "Using deprecated fallback handling for comment $key" );
+                                       $row2 = (object)[ 'comment_text' => $row[$key], 'comment_data' => null ];
+                               } else {
+                                       throw new InvalidArgumentException( "\$row does not contain fields needed for comment $key" );
+                               }
+                       }
+
+                       if ( $row2 ) {
+                               $cid = $row2->comment_id;
+                               $text = $row2->comment_text;
+                               $data = $row2->comment_data;
+                       } elseif ( $this->stage < MIGRATION_NEW && array_key_exists( "{$key}_old", $row ) ) {
+                               $cid = null;
+                               $text = $row["{$key}_old"];
+                               $data = null;
+                       } else {
+                               // @codeCoverageIgnoreStart
+                               wfLogWarning( "Missing comment row for $key, id=$id" );
+                               $cid = null;
+                               $text = '';
+                               $data = null;
+                               // @codeCoverageIgnoreEnd
+                       }
+               }
+
+               $msg = null;
+               if ( $data !== null ) {
+                       $data = FormatJson::decode( $data );
+                       if ( !is_object( $data ) ) {
+                               // @codeCoverageIgnoreStart
+                               wfLogWarning( "Invalid JSON object in comment: $data" );
+                               $data = null;
+                               // @codeCoverageIgnoreEnd
+                       } else {
+                               $data = (array)$data;
+                               if ( isset( $data['_message'] ) ) {
+                                       $msg = self::decodeMessage( $data['_message'] )
+                                               ->setInterfaceMessageFlag( true );
+                               }
+                               if ( !empty( $data['_null'] ) ) {
+                                       $data = null;
+                               } else {
+                                       foreach ( $data as $k => $v ) {
+                                               if ( substr( $k, 0, 1 ) === '_' ) {
+                                                       unset( $data[$k] );
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               return new CommentStoreComment( $cid, $text, $msg, $data );
+       }
+
+       /**
+        * Extract the comment from a row
+        *
+        * Use `self::getJoin()` to ensure the row contains the needed data.
+        *
+        * If you need to fake a comment in a row for some reason, set fields
+        * `{$key}_text` (string) and `{$key}_data` (JSON string or null).
+        *
+        * @param object|array $row Result row.
+        * @param bool $fallback If true, fall back as well as possible instead of throwing an exception.
+        * @return CommentStoreComment
+        */
+       public function getComment( $row, $fallback = false ) {
+               return $this->getCommentInternal( null, $row, $fallback );
+       }
+
+       /**
+        * Extract the comment from a row, with legacy lookups.
+        *
+        * If `$row` might have been generated using `self::getFields()` rather
+        * than `self::getJoin()`, use this. Prefer `self::getComment()` if you
+        * know callers used `self::getJoin()` for the row fetch.
+        *
+        * If you need to fake a comment in a row for some reason, set fields
+        * `{$key}_text` (string) and `{$key}_data` (JSON string or null).
+        *
+        * @param IDatabase $db Database handle to use for lookup
+        * @param object|array $row Result row.
+        * @param bool $fallback If true, fall back as well as possible instead of throwing an exception.
+        * @return CommentStoreComment
+        */
+       public function getCommentLegacy( IDatabase $db, $row, $fallback = false ) {
+               return $this->getCommentInternal( $db, $row, $fallback );
+       }
+
+       /**
+        * Create a new CommentStoreComment, inserting it into the database if necessary
+        *
+        * If a comment is going to be passed to `self::insert()` or the like
+        * multiple times, it will be more efficient to pass a CommentStoreComment
+        * once rather than making `self::insert()` do it every time through.
+        *
+        * @note When passing a CommentStoreComment, this may set `$comment->id` if
+        *  it's not already set. If `$comment->id` is already set, it will not be
+        *  verified that the specified comment actually exists or that it
+        *  corresponds to the comment text, message, and/or data in the
+        *  CommentStoreComment.
+        * @param IDatabase $dbw Database handle to insert on. Unused if `$comment`
+        *  is a CommentStoreComment and `$comment->id` is set.
+        * @param string|Message|CommentStoreComment $comment Comment text or Message object, or
+        *  a CommentStoreComment.
+        * @param array|null $data Structured data to store. Keys beginning with '_' are reserved.
+        *  Ignored if $comment is a CommentStoreComment.
+        * @return CommentStoreComment
+        */
+       public function createComment( IDatabase $dbw, $comment, array $data = null ) {
+               global $wgContLang;
+
+               if ( !$comment instanceof CommentStoreComment ) {
+                       if ( $data !== null ) {
+                               foreach ( $data as $k => $v ) {
+                                       if ( substr( $k, 0, 1 ) === '_' ) {
+                                               throw new InvalidArgumentException( 'Keys in $data beginning with "_" are reserved' );
+                                       }
+                               }
+                       }
+                       if ( $comment instanceof Message ) {
+                               $message = clone $comment;
+                               $text = $message->inLanguage( $wgContLang ) // Avoid $wgForceUIMsgAsContentMsg
+                                       ->setInterfaceMessageFlag( true )
+                                       ->text();
+                               $comment = new CommentStoreComment( null, $text, $message, $data );
+                       } else {
+                               $comment = new CommentStoreComment( null, $comment, null, $data );
+                       }
+               }
+
+               if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
+                       $dbData = $comment->data;
+                       if ( !$comment->message instanceof RawMessage ) {
+                               if ( $dbData === null ) {
+                                       $dbData = [ '_null' => true ];
+                               }
+                               $dbData['_message'] = self::encodeMessage( $comment->message );
+                       }
+                       if ( $dbData !== null ) {
+                               $dbData = FormatJson::encode( (object)$dbData, false, FormatJson::ALL_OK );
+                       }
+
+                       $hash = self::hash( $comment->text, $dbData );
+                       $comment->id = $dbw->selectField(
+                               'comment',
+                               'comment_id',
+                               [
+                                       'comment_hash' => $hash,
+                                       'comment_text' => $comment->text,
+                                       'comment_data' => $dbData,
+                               ],
+                               __METHOD__
+                       );
+                       if ( !$comment->id ) {
+                               $comment->id = $dbw->nextSequenceValue( 'comment_comment_id_seq' );
+                               $dbw->insert(
+                                       'comment',
+                                       [
+                                               'comment_id' => $comment->id,
+                                               'comment_hash' => $hash,
+                                               'comment_text' => $comment->text,
+                                               'comment_data' => $dbData,
+                                       ],
+                                       __METHOD__
+                               );
+                               $comment->id = $dbw->insertId();
+                       }
+               }
+
+               return $comment;
+       }
+
+       /**
+        * Implementation for `self::insert()` and `self::insertWithTempTable()`
+        * @param IDatabase $dbw
+        * @param string|Message|CommentStoreComment $comment
+        * @param array|null $data
+        * @return array [ array $fields, callable $callback ]
+        */
+       private function insertInternal( IDatabase $dbw, $comment, $data ) {
+               $fields = [];
+               $callback = null;
+
+               $comment = $this->createComment( $dbw, $comment, $data );
+
+               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+                       $fields[$this->key] = $comment->text;
+               }
+
+               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+                       if ( isset( self::$tempTables[$this->key] ) ) {
+                               $t = self::$tempTables[$this->key];
+                               $func = __METHOD__;
+                               $commentId = $comment->id;
+                               $callback = function ( $id ) use ( $dbw, $commentId, $t, $func ) {
+                                       $dbw->insert(
+                                               $t['table'],
+                                               [
+                                                       $t['pk'] => $id,
+                                                       $t['field'] => $commentId,
+                                               ],
+                                               $func
+                                       );
+                               };
+                       } else {
+                               $fields["{$this->key}_id"] = $comment->id;
+                       }
+               }
+
+               return [ $fields, $callback ];
+       }
+
+       /**
+        * Prepare for the insertion of a row with a comment
+        *
+        * @note It's recommended to include both the call to this method and the
+        *  row insert in the same transaction.
+        * @param IDatabase $dbw Database handle to insert on
+        * @param string|Message|CommentStoreComment $comment As for `self::createComment()`
+        * @param array|null $data As for `self::createComment()`
+        * @return array Fields for the insert or update
+        */
+       public function insert( IDatabase $dbw, $comment, $data = null ) {
+               if ( isset( self::$tempTables[$this->key] ) ) {
+                       throw new InvalidArgumentException( "Must use insertWithTempTable() for $this->key" );
+               }
+
+               list( $fields ) = $this->insertInternal( $dbw, $comment, $data );
+               return $fields;
+       }
+
+       /**
+        * Prepare for the insertion of a row with a comment and temporary table
+        *
+        * This is currently needed for "rev_comment" and "img_description". In the
+        * future that requirement will be removed.
+        *
+        * @note It's recommended to include both the call to this method and the
+        *  row insert in the same transaction.
+        * @param IDatabase $dbw Database handle to insert on
+        * @param string|Message|CommentStoreComment $comment As for `self::createComment()`
+        * @param array|null $data As for `self::createComment()`
+        * @return array Two values:
+        *  - array Fields for the insert or update
+        *  - callable Function to call when the primary key of the row being
+        *    inserted/updated is known. Pass it that primary key.
+        */
+       public function insertWithTempTable( IDatabase $dbw, $comment, $data = null ) {
+               if ( isset( self::$formerTempTables[$this->key] ) ) {
+                       wfDeprecated( __METHOD__ . " for $this->key", self::$formerTempTables[$this->key] );
+               } elseif ( !isset( self::$tempTables[$this->key] ) ) {
+                       throw new InvalidArgumentException( "Must use insert() for $this->key" );
+               }
+
+               list( $fields, $callback ) = $this->insertInternal( $dbw, $comment, $data );
+               if ( !$callback ) {
+                       $callback = function () {
+                               // Do nothing.
+                       };
+               }
+               return [ $fields, $callback ];
+       }
+
+       /**
+        * Encode a Message as a PHP data structure
+        * @param Message $msg
+        * @return array
+        */
+       protected static function encodeMessage( Message $msg ) {
+               $key = count( $msg->getKeysToTry() ) > 1 ? $msg->getKeysToTry() : $msg->getKey();
+               $params = $msg->getParams();
+               foreach ( $params as &$param ) {
+                       if ( $param instanceof Message ) {
+                               $param = [
+                                       'message' => self::encodeMessage( $param )
+                               ];
+                       }
+               }
+               array_unshift( $params, $key );
+               return $params;
+       }
+
+       /**
+        * Decode a message that was encoded by self::encodeMessage()
+        * @param array $data
+        * @return Message
+        */
+       protected static function decodeMessage( $data ) {
+               $key = array_shift( $data );
+               foreach ( $data as &$param ) {
+                       if ( is_object( $param ) ) {
+                               $param = (array)$param;
+                       }
+                       if ( is_array( $param ) && count( $param ) === 1 && isset( $param['message'] ) ) {
+                               $param = self::decodeMessage( $param['message'] );
+                       }
+               }
+               return new Message( $key, $data );
+       }
+
+       /**
+        * Hashing function for comment storage
+        * @param string $text Comment text
+        * @param string|null $data Comment data
+        * @return int 32-bit signed integer
+        */
+       public static function hash( $text, $data ) {
+               $hash = crc32( $text ) ^ crc32( (string)$data );
+
+               // 64-bit PHP returns an unsigned CRC, change it to signed for
+               // insertion into the database.
+               if ( $hash >= 0x80000000 ) {
+                       $hash |= -1 << 32;
+               }
+
+               return $hash;
+       }
+
+}
diff --git a/includes/CommentStoreComment.php b/includes/CommentStoreComment.php
new file mode 100644 (file)
index 0000000..afc1374
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Value object for CommentStore
+ *
+ * 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 Wikimedia\Rdbms\IDatabase;
+
+/**
+ * CommentStoreComment represents a comment stored by CommentStore. The fields
+ * should be considered read-only.
+ * @since 1.30
+ */
+class CommentStoreComment {
+
+       /** @var int|null Comment ID, if any */
+       public $id;
+
+       /** @var string Text version of the comment */
+       public $text;
+
+       /** @var Message Message version of the comment. Might be a RawMessage */
+       public $message;
+
+       /** @var array|null Structured data of the comment */
+       public $data;
+
+       /**
+        * @private For use by CommentStore only
+        * @param int|null $id
+        * @param string $text
+        * @param Message|null $message
+        * @param array|null $data
+        */
+       public function __construct( $id, $text, Message $message = null, array $data = null ) {
+               $this->id = $id;
+               $this->text = $text;
+               $this->message = $message ?: new RawMessage( '$1', [ $text ] );
+               $this->data = $data;
+       }
+}
index 44461b3..cf3e569 100644 (file)
@@ -8763,6 +8763,13 @@ $wgExperiencedUserMemberSince = 30; # days
  */
 $wgInterwikiPrefixDisplayTypes = [];
 
+/**
+ * Comment table schema migration stage.
+ * @since 1.30
+ * @var int One of the MIGRATION_* constants
+ */
+$wgCommentTableSchemaMigrationStage = MIGRATION_OLD;
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 48e694c..6ea293c 100644 (file)
@@ -2658,8 +2658,7 @@ class EditPage {
                $wgOut->addHTML( Html::openElement(
                        'form',
                        [
-                               // Keep mw-editform-ooui class for backwards-compatibility temporarily
-                               'class' => 'mw-editform mw-editform-ooui',
+                               'class' => 'mw-editform',
                                'id' => self::EDITFORM_ID,
                                'name' => self::EDITFORM_ID,
                                'method' => 'post',
@@ -2702,7 +2701,7 @@ class EditPage {
 
                if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
                        $username = $this->lastDelete->user_name;
-                       $comment = $this->lastDelete->log_comment;
+                       $comment = CommentStore::newKey( 'log_comment' )->getComment( $this->lastDelete )->text;
 
                        // It is better to not parse the comment at all than to have templates expanded in the middle
                        // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
@@ -3222,7 +3221,7 @@ class EditPage {
                $wgOut->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
                $wgOut->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
                $wgOut->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
-               $wgOut->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop ) );
+               $wgOut->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
 
                if ( !$this->checkUnicodeCompliantBrowser() ) {
                        $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
@@ -3744,8 +3743,9 @@ class EditPage {
         */
        protected function getLastDelete() {
                $dbr = wfGetDB( DB_REPLICA );
+               $commentQuery = CommentStore::newKey( 'log_comment' )->getJoin();
                $data = $dbr->selectRow(
-                       [ 'logging', 'user' ],
+                       [ 'logging', 'user' ] + $commentQuery['tables'],
                        [
                                'log_type',
                                'log_action',
@@ -3753,11 +3753,10 @@ class EditPage {
                                'log_user',
                                'log_namespace',
                                'log_title',
-                               'log_comment',
                                'log_params',
                                'log_deleted',
                                'user_name'
-                       ], [
+                       ] + $commentQuery['fields'], [
                                'log_namespace' => $this->mTitle->getNamespace(),
                                'log_title' => $this->mTitle->getDBkey(),
                                'log_type' => 'delete',
@@ -3765,7 +3764,10 @@ class EditPage {
                                'user_id=log_user'
                        ],
                        __METHOD__,
-                       [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ]
+                       [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
+                       [
+                               'user' => [ 'JOIN', 'user_id=log_user' ],
+                       ] + $commentQuery['joins']
                );
                // Quick paranoid permission checks...
                if ( is_object( $data ) ) {
@@ -3774,7 +3776,8 @@ class EditPage {
                        }
 
                        if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
-                               $data->log_comment = $this->context->msg( 'rev-deleted-comment' )->escaped();
+                               $data->log_comment_text = $this->context->msg( 'rev-deleted-comment' )->escaped();
+                               $data->log_comment_data = null;
                        }
                }
 
@@ -4139,7 +4142,7 @@ class EditPage {
         *   where bool indicates the checked status of the checkbox
         * @return array
         */
-       protected function getCheckboxesDefinition( $checked ) {
+       public function getCheckboxesDefinition( $checked ) {
                global $wgUser;
                $checkboxes = [];
 
index 96a88d3..0def6a0 100644 (file)
@@ -72,7 +72,8 @@ class FeedUtils {
        /**
         * Format a diff for the newsfeed
         *
-        * @param object $row Row from the recentchanges table
+        * @param object $row Row from the recentchanges table, including fields as
+        *  appropriate for CommentStore
         * @return string
         */
        public static function formatDiff( $row ) {
@@ -88,7 +89,9 @@ class FeedUtils {
                        $timestamp,
                        $row->rc_deleted & Revision::DELETED_COMMENT
                                ? wfMessage( 'rev-deleted-comment' )->escaped()
-                               : $row->rc_comment,
+                               : CommentStore::newKey( 'rc_comment' )
+                                       // Legacy from RecentChange::selectFields() via ChangesListSpecialPage::doMainQuery()
+                                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row )->text,
                        $actiontext
                );
        }
index d55f0e0..aedb704 100644 (file)
@@ -1624,14 +1624,16 @@ class Linker {
                $link, $fallbackAnchor = false
        ) {
                $anchorEscaped = htmlspecialchars( $anchor );
-               $ret = "<h$level$attribs"
-                       . "<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
-                       . $link
-                       . "</h$level>";
+               $fallback = '';
                if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
                        $fallbackAnchor = htmlspecialchars( $fallbackAnchor );
-                       $ret = "<div id=\"$fallbackAnchor\"></div>$ret";
+                       $fallback = "<span id=\"$fallbackAnchor\"></span>";
                }
+               $ret = "<h$level$attribs"
+                       . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
+                       . $link
+                       . "</h$level>";
+
                return $ret;
        }
 
index 7461191..5856e21 100644 (file)
@@ -95,13 +95,22 @@ class MagicWordArray {
        public function getBaseRegex() {
                if ( is_null( $this->baseRegex ) ) {
                        $this->baseRegex = [ 0 => '', 1 => '' ];
+                       $allGroups = [];
                        foreach ( $this->names as $name ) {
                                $magic = MagicWord::get( $name );
                                $case = intval( $magic->isCaseSensitive() );
                                foreach ( $magic->getSynonyms() as $i => $syn ) {
                                        // Group name must start with a non-digit in PCRE 8.34+
                                        $it = strtr( $i, '0123456789', 'abcdefghij' );
-                                       $group = "(?P<{$it}_{$name}>" . preg_quote( $syn, '/' ) . ')';
+                                       $groupName = $it . '_' . $name;
+                                       $group = '(?P<' . $groupName . '>' . preg_quote( $syn, '/' ) . ')';
+                                       // look for same group names to avoid same named subpatterns in the regex
+                                       if ( isset( $allGroups[$groupName] ) ) {
+                                               throw new MWException(
+                                                       __METHOD__ . ': duplicate internal name in magic word array: ' . $name
+                                               );
+                                       }
+                                       $allGroups[$groupName] = true;
                                        if ( $this->baseRegex[$case] === '' ) {
                                                $this->baseRegex[$case] = $group;
                                        } else {
index dac756e..ff8deee 100644 (file)
@@ -242,6 +242,8 @@ class PageProps {
        private function getGoodIDs( $titles ) {
                $result = [];
                if ( is_array( $titles ) ) {
+                       ( new LinkBatch( $titles ) )->execute();
+
                        foreach ( $titles as $title ) {
                                $pageID = $title->getArticleID();
                                if ( $pageID > 0 ) {
index e457beb..ff4a284 100644 (file)
@@ -192,7 +192,9 @@ class Revision implements IDBAccessObject {
                $attribs = $overrides + [
                        'page'       => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
                        'id'         => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
-                       'comment'    => $row->ar_comment,
+                       'comment'    => CommentStore::newKey( 'ar_comment' )
+                               // Legacy because $row probably came from self::selectArchiveFields()
+                               ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text,
                        'user'       => $row->ar_user,
                        'user_text'  => $row->ar_user_text,
                        'timestamp'  => $row->ar_timestamp,
@@ -443,6 +445,8 @@ class Revision implements IDBAccessObject {
        /**
         * Return the list of revision fields that should be selected to create
         * a new revision.
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        public static function selectFields() {
@@ -453,7 +457,6 @@ class Revision implements IDBAccessObject {
                        'rev_page',
                        'rev_text_id',
                        'rev_timestamp',
-                       'rev_comment',
                        'rev_user_text',
                        'rev_user',
                        'rev_minor_edit',
@@ -463,6 +466,8 @@ class Revision implements IDBAccessObject {
                        'rev_sha1',
                ];
 
+               $fields += CommentStore::newKey( 'rev_comment' )->getFields();
+
                if ( $wgContentHandlerUseDB ) {
                        $fields[] = 'rev_content_format';
                        $fields[] = 'rev_content_model';
@@ -474,6 +479,8 @@ class Revision implements IDBAccessObject {
        /**
         * Return the list of revision fields that should be selected to create
         * a new revision from an archive row.
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        public static function selectArchiveFields() {
@@ -485,7 +492,6 @@ class Revision implements IDBAccessObject {
                        'ar_text',
                        'ar_text_id',
                        'ar_timestamp',
-                       'ar_comment',
                        'ar_user_text',
                        'ar_user',
                        'ar_minor_edit',
@@ -495,6 +501,8 @@ class Revision implements IDBAccessObject {
                        'ar_sha1',
                ];
 
+               $fields += CommentStore::newKey( 'ar_comment' )->getFields();
+
                if ( $wgContentHandlerUseDB ) {
                        $fields[] = 'ar_content_format';
                        $fields[] = 'ar_content_model';
@@ -568,7 +576,9 @@ class Revision implements IDBAccessObject {
                        $this->mId = intval( $row->rev_id );
                        $this->mPage = intval( $row->rev_page );
                        $this->mTextId = intval( $row->rev_text_id );
-                       $this->mComment = $row->rev_comment;
+                       $this->mComment = CommentStore::newKey( 'rev_comment' )
+                               // Legacy because $row probably came from self::selectFields()
+                               ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
                        $this->mUser = intval( $row->rev_user );
                        $this->mMinorEdit = intval( $row->rev_minor_edit );
                        $this->mTimestamp = $row->rev_timestamp;
@@ -1455,7 +1465,6 @@ class Revision implements IDBAccessObject {
                        'rev_id'         => $rev_id,
                        'rev_page'       => $this->mPage,
                        'rev_text_id'    => $this->mTextId,
-                       'rev_comment'    => $this->mComment,
                        'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
                        'rev_user'       => $this->mUser,
                        'rev_user_text'  => $this->mUserText,
@@ -1470,6 +1479,10 @@ class Revision implements IDBAccessObject {
                                : $this->mSha1,
                ];
 
+               list( $commentFields, $commentCallback ) =
+                       CommentStore::newKey( 'rev_comment' )->insertWithTempTable( $dbw, $this->mComment );
+               $row += $commentFields;
+
                if ( $wgContentHandlerUseDB ) {
                        // NOTE: Store null for the default model and format, to save space.
                        // XXX: Makes the DB sensitive to changed defaults.
@@ -1498,6 +1511,7 @@ class Revision implements IDBAccessObject {
                        // Only if nextSequenceValue() was called
                        $this->mId = $dbw->insertId();
                }
+               $commentCallback( $this->mId );
 
                // Assertion to try to catch T92046
                if ( (int)$this->mId === 0 ) {
index 05f85fa..0687a15 100644 (file)
@@ -1036,6 +1036,7 @@ class Title implements LinkTarget {
         * Can this title have a corresponding talk page?
         *
         * @see MWNamespace::hasTalkNamespace
+        * @since 1.30
         *
         * @return bool True if this title either is a talk page or can have a talk page associated.
         */
@@ -1324,7 +1325,7 @@ class Title implements LinkTarget {
         *
         * @since 1.30
         *
-        * @return Title The object for the talk page,
+        * @return Title|null The object for the talk page,
         *         or null if no associated talk page can exist, according to canHaveTalkPage().
         */
        public function getTalkPageIfDefined() {
@@ -2669,24 +2670,33 @@ class Title implements LinkTarget {
 
                if ( $this->mTitleProtection === null ) {
                        $dbr = wfGetDB( DB_REPLICA );
+                       $commentStore = new CommentStore( 'pt_reason' );
+                       $commentQuery = $commentStore->getJoin();
                        $res = $dbr->select(
-                               'protected_titles',
+                               [ 'protected_titles' ] + $commentQuery['tables'],
                                [
                                        'user' => 'pt_user',
-                                       'reason' => 'pt_reason',
                                        'expiry' => 'pt_expiry',
                                        'permission' => 'pt_create_perm'
-                               ],
+                               ] + $commentQuery['fields'],
                                [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
-                               __METHOD__
+                               __METHOD__,
+                               [],
+                               $commentQuery['joins']
                        );
 
                        // fetchRow returns false if there are no rows.
                        $row = $dbr->fetchRow( $res );
                        if ( $row ) {
-                               $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
+                               $this->mTitleProtection = [
+                                       'user' => $row['user'],
+                                       'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
+                                       'permission' => $row['permission'],
+                                       'reason' => $commentStore->getComment( $row )->text,
+                               ];
+                       } else {
+                               $this->mTitleProtection = false;
                        }
-                       $this->mTitleProtection = $row;
                }
                return $this->mTitleProtection;
        }
index 1fafb24..d0f45be 100644 (file)
@@ -55,6 +55,10 @@ class WatchedItemQueryService {
        /** @var WatchedItemQueryServiceExtension[]|null */
        private $extensions = null;
 
+       /**
+        * @var CommentStore|null */
+       private $commentStore = null;
+
        public function __construct( LoadBalancer $loadBalancer ) {
                $this->loadBalancer = $loadBalancer;
        }
@@ -78,6 +82,13 @@ class WatchedItemQueryService {
                return $this->loadBalancer->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
        }
 
+       private function getCommentStore() {
+               if ( !$this->commentStore ) {
+                       $this->commentStore = new CommentStore( 'rc_comment' );
+               }
+               return $this->commentStore;
+       }
+
        /**
         * @param User $user
         * @param array $options Allowed keys:
@@ -172,13 +183,9 @@ class WatchedItemQueryService {
                        );
                }
 
-               $tables = [ 'recentchanges', 'watchlist' ];
-               if ( !$options['allRevisions'] ) {
-                       $tables[] = 'page';
-               }
-
                $db = $this->getConnection();
 
+               $tables = $this->getWatchedItemsWithRCInfoQueryTables( $options );
                $fields = $this->getWatchedItemsWithRCInfoQueryFields( $options );
                $conds = $this->getWatchedItemsWithRCInfoQueryConds( $db, $user, $options );
                $dbOptions = $this->getWatchedItemsWithRCInfoQueryDbOptions( $options );
@@ -320,6 +327,17 @@ class WatchedItemQueryService {
                return array_intersect_key( $allFields, array_flip( $rcKeys ) );
        }
 
+       private function getWatchedItemsWithRCInfoQueryTables( array $options ) {
+               $tables = [ 'recentchanges', 'watchlist' ];
+               if ( !$options['allRevisions'] ) {
+                       $tables[] = 'page';
+               }
+               if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
+                       $tables += $this->getCommentStore()->getJoin()['tables'];
+               }
+               return $tables;
+       }
+
        private function getWatchedItemsWithRCInfoQueryFields( array $options ) {
                $fields = [
                        'rc_id',
@@ -355,7 +373,7 @@ class WatchedItemQueryService {
                        $fields[] = 'rc_user';
                }
                if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
-                       $fields[] = 'rc_comment';
+                       $fields += $this->getCommentStore()->getJoin()['fields'];
                }
                if ( in_array( self::INCLUDE_PATROL_INFO, $options['includeFields'] ) ) {
                        $fields = array_merge( $fields, [ 'rc_patrolled', 'rc_log_type' ] );
@@ -657,6 +675,9 @@ class WatchedItemQueryService {
                if ( !$options['allRevisions'] ) {
                        $joinConds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
                }
+               if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
+                       $joinConds += $this->getCommentStore()->getJoin()['joins'];
+               }
                return $joinConds;
        }
 
index 1fb034f..e7b2808 100644 (file)
@@ -59,7 +59,7 @@ class ApiMove extends ApiBase {
                if ( !$toTitle || $toTitle->isExternal() ) {
                        $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
                }
-               $toTalk = $toTitle->canTalk() ? $toTitle->getTalkPage() : null;
+               $toTalk = $toTitle->getTalkPageIfDefined();
 
                if ( $toTitle->getNamespace() == NS_FILE
                        && !RepoGroup::singleton()->getLocalRepo()->findFile( $toTitle )
index fd95e17..d594ad4 100644 (file)
@@ -49,6 +49,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
                $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
 
                $db = $this->getDB();
+               $commentStore = new CommentStore( 'ipb_reason' );
 
                $prop = $params['prop'];
                if ( !is_null( $prop ) ) {
@@ -263,7 +264,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
                                $data['blockedby'] = $row->ipb_by_text;
                                $data['blockedbyid'] = (int)$row->ipb_by;
                                $data['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
-                               $data['blockreason'] = $row->ipb_reason;
+                               $data['blockreason'] = $commentStore->getComment( $row )->text;
                                $data['blockexpiry'] = $row->ipb_expiry;
                        }
                        if ( $row->ipb_deleted ) {
index 44526e8..fe16134 100644 (file)
@@ -456,10 +456,13 @@ abstract class ApiQueryBase extends ApiBase {
                                'ipb_id',
                                'ipb_by',
                                'ipb_by_text',
-                               'ipb_reason',
                                'ipb_expiry',
                                'ipb_timestamp'
                        ] );
+                       $commentQuery = CommentStore::newKey( 'ipb_reason' )->getJoin();
+                       $this->addTables( $commentQuery['tables'] );
+                       $this->addFields( $commentQuery['fields'] );
+                       $this->addJoinConds( $commentQuery['joins'] );
                }
 
                // Don't show hidden names
index 076a09e..698c13c 100644 (file)
@@ -37,6 +37,7 @@ class ApiQueryBlocks extends ApiQueryBase {
 
        public function execute() {
                $db = $this->getDB();
+               $commentStore = new CommentStore( 'ipb_reason' );
                $params = $this->extractRequestParams();
                $this->requireMaxOneParameter( $params, 'users', 'ip' );
 
@@ -61,12 +62,18 @@ class ApiQueryBlocks extends ApiQueryBase {
                $this->addFieldsIf( 'ipb_by_text', $fld_by );
                $this->addFieldsIf( 'ipb_by', $fld_byid );
                $this->addFieldsIf( 'ipb_expiry', $fld_expiry );
-               $this->addFieldsIf( 'ipb_reason', $fld_reason );
                $this->addFieldsIf( [ 'ipb_range_start', 'ipb_range_end' ], $fld_range );
                $this->addFieldsIf( [ 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock',
                        'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ],
                        $fld_flags );
 
+               if ( $fld_reason ) {
+                       $commentQuery = $commentStore->getJoin();
+                       $this->addTables( $commentQuery['tables'] );
+                       $this->addFields( $commentQuery['fields'] );
+                       $this->addJoinConds( $commentQuery['joins'] );
+               }
+
                $this->addOption( 'LIMIT', $params['limit'] + 1 );
                $this->addTimestampWhereRange(
                        'ipb_timestamp',
@@ -205,7 +212,7 @@ class ApiQueryBlocks extends ApiQueryBase {
                                $block['expiry'] = ApiResult::formatExpiry( $row->ipb_expiry );
                        }
                        if ( $fld_reason ) {
-                               $block['reason'] = $row->ipb_reason;
+                               $block['reason'] = $commentStore->getComment( $row )->text;
                        }
                        if ( $fld_range && !$row->ipb_auto ) {
                                $block['rangestart'] = IP::formatHex( $row->ipb_range_start );
index b68a868..5dd007b 100644 (file)
@@ -44,6 +44,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
 
                $user = $this->getUser();
                $db = $this->getDB();
+               $commentStore = new CommentStore( 'ar_comment' );
                $params = $this->extractRequestParams( false );
                $prop = array_flip( $params['prop'] );
                $fld_parentid = isset( $prop['parentid'] );
@@ -115,11 +116,17 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                $this->addFieldsIf( 'ar_rev_id', $fld_revid );
                $this->addFieldsIf( 'ar_user_text', $fld_user );
                $this->addFieldsIf( 'ar_user', $fld_userid );
-               $this->addFieldsIf( 'ar_comment', $fld_comment || $fld_parsedcomment );
                $this->addFieldsIf( 'ar_minor_edit', $fld_minor );
                $this->addFieldsIf( 'ar_len', $fld_len );
                $this->addFieldsIf( 'ar_sha1', $fld_sha1 );
 
+               if ( $fld_comment || $fld_parsedcomment ) {
+                       $commentQuery = $commentStore->getJoin();
+                       $this->addTables( $commentQuery['tables'] );
+                       $this->addFields( $commentQuery['fields'] );
+                       $this->addJoinConds( $commentQuery['joins'] );
+               }
+
                if ( $fld_tags ) {
                        $this->addTables( 'tag_summary' );
                        $this->addJoinConds(
@@ -322,12 +329,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                                        $anyHidden = true;
                                }
                                if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_COMMENT, $user ) ) {
+                                       $comment = $commentStore->getComment( $row )->text;
                                        if ( $fld_comment ) {
-                                               $rev['comment'] = $row->ar_comment;
+                                               $rev['comment'] = $comment;
                                        }
                                        if ( $fld_parsedcomment ) {
                                                $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
-                                               $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
+                                               $rev['parsedcomment'] = Linker::formatComment( $comment, $title );
                                        }
                                }
                        }
index 7383cba..212b613 100644 (file)
@@ -43,6 +43,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
 
                $user = $this->getUser();
                $db = $this->getDB();
+               $commentStore = new CommentStore( 'fa_description' );
 
                $params = $this->extractRequestParams();
 
@@ -66,13 +67,19 @@ class ApiQueryFilearchive extends ApiQueryBase {
                $this->addFieldsIf( 'fa_sha1', $fld_sha1 );
                $this->addFieldsIf( [ 'fa_user', 'fa_user_text' ], $fld_user );
                $this->addFieldsIf( [ 'fa_height', 'fa_width', 'fa_size' ], $fld_dimensions || $fld_size );
-               $this->addFieldsIf( 'fa_description', $fld_description );
                $this->addFieldsIf( [ 'fa_major_mime', 'fa_minor_mime' ], $fld_mime );
                $this->addFieldsIf( 'fa_media_type', $fld_mediatype );
                $this->addFieldsIf( 'fa_metadata', $fld_metadata );
                $this->addFieldsIf( 'fa_bits', $fld_bitdepth );
                $this->addFieldsIf( 'fa_archive_name', $fld_archivename );
 
+               if ( $fld_description ) {
+                       $commentQuery = $commentStore->getJoin();
+                       $this->addTables( $commentQuery['tables'] );
+                       $this->addFields( $commentQuery['fields'] );
+                       $this->addJoinConds( $commentQuery['joins'] );
+               }
+
                if ( !is_null( $params['continue'] ) ) {
                        $cont = explode( '|', $params['continue'] );
                        $this->dieContinueUsageIf( count( $cont ) != 3 );
@@ -165,10 +172,10 @@ class ApiQueryFilearchive extends ApiQueryBase {
                        if ( $fld_description &&
                                Revision::userCanBitfield( $row->fa_deleted, File::DELETED_COMMENT, $user )
                        ) {
-                               $file['description'] = $row->fa_description;
+                               $file['description'] = $commentStore->getComment( $row )->text;
                                if ( isset( $prop['parseddescription'] ) ) {
                                        $file['parseddescription'] = Linker::formatComment(
-                                               $row->fa_description, $title );
+                                               $file['description'], $title );
                                }
                        }
                        if ( $fld_user &&
index 3639c06..d29a763 100644 (file)
@@ -137,7 +137,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
 
                $order[] = $this->prefix . '_title' . $sort;
                $this->addOption( 'ORDER BY', $order );
-               $this->addOption( 'USE INDEX', $this->prefix . '_from' );
+               $this->addOption( 'USE INDEX', [ $this->table => 'PRIMARY' ] );
                $this->addOption( 'LIMIT', $params['limit'] + 1 );
 
                $res = $this->select( __METHOD__ );
index 3e8bccc..3066720 100644 (file)
@@ -31,6 +31,8 @@
  */
 class ApiQueryLogEvents extends ApiQueryBase {
 
+       private $commentStore;
+
        public function __construct( ApiQuery $query, $moduleName ) {
                parent::__construct( $query, $moduleName, 'le' );
        }
@@ -43,6 +45,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
        public function execute() {
                $params = $this->extractRequestParams();
                $db = $this->getDB();
+               $this->commentStore = new CommentStore( 'log_comment' );
                $this->requireMaxOneParameter( $params, 'title', 'prefix', 'namespace' );
 
                $prop = array_flip( $params['prop'] );
@@ -91,9 +94,15 @@ class ApiQueryLogEvents extends ApiQueryBase {
                        [ 'log_namespace', 'log_title' ],
                        $this->fld_title || $this->fld_parsedcomment
                );
-               $this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment );
                $this->addFieldsIf( 'log_params', $this->fld_details );
 
+               if ( $this->fld_comment || $this->fld_parsedcomment ) {
+                       $commentQuery = $this->commentStore->getJoin();
+                       $this->addTables( $commentQuery['tables'] );
+                       $this->addFields( $commentQuery['fields'] );
+                       $this->addJoinConds( $commentQuery['joins'] );
+               }
+
                if ( $this->fld_tags ) {
                        $this->addTables( 'tag_summary' );
                        $this->addJoinConds( [ 'tag_summary' => [ 'LEFT JOIN', 'log_id=ts_log_id' ] ] );
@@ -327,18 +336,19 @@ class ApiQueryLogEvents extends ApiQueryBase {
                        $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->log_timestamp );
                }
 
-               if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->log_comment ) ) {
+               if ( $this->fld_comment || $this->fld_parsedcomment ) {
                        if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
                                $vals['commenthidden'] = true;
                                $anyHidden = true;
                        }
                        if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $user ) ) {
+                               $comment = $this->commentStore->getComment( $row )->text;
                                if ( $this->fld_comment ) {
-                                       $vals['comment'] = $row->log_comment;
+                                       $vals['comment'] = $comment;
                                }
 
                                if ( $this->fld_parsedcomment ) {
-                                       $vals['parsedcomment'] = Linker::formatComment( $row->log_comment, $title );
+                                       $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
                                }
                        }
                }
index 5f6510e..b69a299 100644 (file)
@@ -55,10 +55,17 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
 
                $prop = array_flip( $params['prop'] );
                $this->addFieldsIf( 'pt_user', isset( $prop['user'] ) || isset( $prop['userid'] ) );
-               $this->addFieldsIf( 'pt_reason', isset( $prop['comment'] ) || isset( $prop['parsedcomment'] ) );
                $this->addFieldsIf( 'pt_expiry', isset( $prop['expiry'] ) );
                $this->addFieldsIf( 'pt_create_perm', isset( $prop['level'] ) );
 
+               if ( isset( $prop['comment'] ) || isset( $prop['parsedcomment'] ) ) {
+                       $commentStore = new CommentStore( 'pt_reason' );
+                       $commentQuery = $commentStore->getJoin();
+                       $this->addTables( $commentQuery['tables'] );
+                       $this->addFields( $commentQuery['fields'] );
+                       $this->addJoinConds( $commentQuery['joins'] );
+               }
+
                $this->addTimestampWhereRange( 'pt_timestamp', $params['dir'], $params['start'], $params['end'] );
                $this->addWhereFld( 'pt_namespace', $params['namespace'] );
                $this->addWhereFld( 'pt_create_perm', $params['level'] );
@@ -127,11 +134,13 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
                                }
 
                                if ( isset( $prop['comment'] ) ) {
-                                       $vals['comment'] = $row->pt_reason;
+                                       $vals['comment'] = $commentStore->getComment( $row )->text;
                                }
 
                                if ( isset( $prop['parsedcomment'] ) ) {
-                                       $vals['parsedcomment'] = Linker::formatComment( $row->pt_reason, $title );
+                                       $vals['parsedcomment'] = Linker::formatComment(
+                                               $commentStore->getComment( $row )->text, $titles
+                                       );
                                }
 
                                if ( isset( $prop['expiry'] ) ) {
index 0dc01aa..9af4e3e 100644 (file)
@@ -36,6 +36,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                parent::__construct( $query, $moduleName, 'rc' );
        }
 
+       private $commentStore;
+
        private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
                $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
                $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
@@ -274,7 +276,6 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
 
                        /* Add fields to our query if they are specified as a needed parameter. */
                        $this->addFieldsIf( [ 'rc_this_oldid', 'rc_last_oldid' ], $this->fld_ids );
-                       $this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
                        $this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid );
                        $this->addFieldsIf( 'rc_user_text', $this->fld_user );
                        $this->addFieldsIf( [ 'rc_minor', 'rc_type', 'rc_bot' ], $this->fld_flags );
@@ -286,6 +287,14 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                        );
                        $showRedirects = $this->fld_redirect || isset( $show['redirect'] )
                                || isset( $show['!redirect'] );
+
+                       if ( $this->fld_comment || $this->fld_parsedcomment ) {
+                               $this->commentStore = new CommentStore( 'rc_comment' );
+                               $commentQuery = $this->commentStore->getJoin();
+                               $this->addTables( $commentQuery['tables'] );
+                               $this->addFields( $commentQuery['fields'] );
+                               $this->addJoinConds( $commentQuery['joins'] );
+                       }
                }
                $this->addFieldsIf( [ 'rc_this_oldid' ],
                        $resultPageSet && $params['generaterevisions'] );
@@ -500,12 +509,13 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                                $anyHidden = true;
                        }
                        if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
-                               if ( $this->fld_comment && isset( $row->rc_comment ) ) {
-                                       $vals['comment'] = $row->rc_comment;
+                               $comment = $this->commentStore->getComment( $row )->text;
+                               if ( $this->fld_comment ) {
+                                       $vals['comment'] = $comment;
                                }
 
-                               if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
-                                       $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+                               if ( $this->fld_parsedcomment ) {
+                                       $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
                                }
                        }
                }
index 181cddb..bb0f335 100644 (file)
@@ -36,7 +36,7 @@ class ApiQueryContributions extends ApiQueryBase {
        }
 
        private $params, $prefixMode, $userprefix, $multiUserMode, $idMode, $usernames, $userids,
-               $parentLens;
+               $parentLens, $commentStore;
        private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
                $fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
                $fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
@@ -45,6 +45,8 @@ class ApiQueryContributions extends ApiQueryBase {
                // Parse some parameters
                $this->params = $this->extractRequestParams();
 
+               $this->commentStore = new CommentStore( 'rev_comment' );
+
                $prop = array_flip( $this->params['prop'] );
                $this->fld_ids = isset( $prop['ids'] );
                $this->fld_title = isset( $prop['title'] );
@@ -341,12 +343,18 @@ class ApiQueryContributions extends ApiQueryBase {
                $this->addFieldsIf( 'rev_page', $this->fld_ids );
                $this->addFieldsIf( 'page_latest', $this->fld_flags );
                // $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed?
-               $this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment );
                $this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff );
                $this->addFieldsIf( 'rev_minor_edit', $this->fld_flags );
                $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff || $this->fld_ids );
                $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
 
+               if ( $this->fld_comment || $this->fld_parsedcomment ) {
+                       $commentQuery = $this->commentStore->getJoin();
+                       $this->addTables( $commentQuery['tables'] );
+                       $this->addFields( $commentQuery['fields'] );
+                       $this->addJoinConds( $commentQuery['joins'] );
+               }
+
                if ( $this->fld_tags ) {
                        $this->addTables( 'tag_summary' );
                        $this->addJoinConds(
@@ -416,7 +424,7 @@ class ApiQueryContributions extends ApiQueryBase {
                        $vals['top'] = $row->page_latest == $row->rev_id;
                }
 
-               if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) {
+               if ( $this->fld_comment || $this->fld_parsedcomment ) {
                        if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
                                $vals['commenthidden'] = true;
                                $anyHidden = true;
@@ -428,12 +436,13 @@ class ApiQueryContributions extends ApiQueryBase {
                        );
 
                        if ( $userCanView ) {
+                               $comment = $this->commentStore->getComment( $row )->text;
                                if ( $this->fld_comment ) {
-                                       $vals['comment'] = $row->rev_comment;
+                                       $vals['comment'] = $comment;
                                }
 
                                if ( $this->fld_parsedcomment ) {
-                                       $vals['parsedcomment'] = Linker::formatComment( $row->rev_comment, $title );
+                                       $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
                                }
                        }
                }
index 2a0eadd..fbf1f9e 100644 (file)
@@ -99,6 +99,7 @@ class ApiQueryUsers extends ApiQueryBase {
 
        public function execute() {
                $db = $this->getDB();
+               $commentStore = new CommentStore( 'ipb_reason' );
 
                $params = $this->extractRequestParams();
                $this->requireMaxOneParameter( $params, 'userids', 'users' );
@@ -236,7 +237,7 @@ class ApiQueryUsers extends ApiQueryBase {
                                        $data[$key]['blockedby'] = $row->ipb_by_text;
                                        $data[$key]['blockedbyid'] = (int)$row->ipb_by;
                                        $data[$key]['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
-                                       $data[$key]['blockreason'] = $row->ipb_reason;
+                                       $data[$key]['blockreason'] = $commentStore->getComment( $row )->text;
                                        $data[$key]['blockexpiry'] = $row->ipb_expiry;
                                }
 
index 9883480..2ab8524 100644 (file)
@@ -34,6 +34,8 @@ use MediaWiki\MediaWikiServices;
  */
 class ApiQueryWatchlist extends ApiQueryGeneratorBase {
 
+       private $commentStore;
+
        public function __construct( ApiQuery $query, $moduleName ) {
                parent::__construct( $query, $moduleName, 'wl' );
        }
@@ -85,6 +87,10 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                                        $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'patrol' );
                                }
                        }
+
+                       if ( $this->fld_comment || $this->fld_parsedcomment ) {
+                               $this->commentStore = new CommentStore( 'rc_comment' );
+                       }
                }
 
                $options = [
@@ -353,12 +359,13 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                                Revision::DELETED_COMMENT,
                                $user
                        ) ) {
-                               if ( $this->fld_comment && isset( $recentChangeInfo['rc_comment'] ) ) {
-                                       $vals['comment'] = $recentChangeInfo['rc_comment'];
+                               $comment = $this->commentStore->getComment( $recentChangeInfo )->text;
+                               if ( $this->fld_comment ) {
+                                       $vals['comment'] = $comment;
                                }
 
-                               if ( $this->fld_parsedcomment && isset( $recentChangeInfo['rc_comment'] ) ) {
-                                       $vals['parsedcomment'] = Linker::formatComment( $recentChangeInfo['rc_comment'], $title );
+                               if ( $this->fld_parsedcomment ) {
+                                       $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
                                }
                        }
                }
index 6272f4e..9b6b81d 100644 (file)
        "apihelp-expandtemplates-param-generatexml": "Generar un árbol de análisis XML (remplazado por $1prop=parsetree).",
        "apihelp-expandtemplates-example-simple": "Expandir el wikitexto <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>.",
        "apihelp-feedcontributions-summary": "Devuelve el canal de contribuciones de un usuario.",
-       "apihelp-feedcontributions-param-feedformat": "El formato del canal.",
+       "apihelp-feedcontributions-param-feedformat": "El formato del suministro.",
        "apihelp-feedcontributions-param-user": "De qué usuarios recibir contribuciones.",
        "apihelp-feedcontributions-param-namespace": "Espacio de nombre para filtrar las contribuciones.",
        "apihelp-feedcontributions-param-year": "A partir del año (y anteriores).",
        "apihelp-feedcontributions-param-showsizediff": "Mostrar la diferencia de tamaño entre revisiones.",
        "apihelp-feedcontributions-example-simple": "Devolver las contribuciones del usuario <kbd>Example</kbd>.",
        "apihelp-feedrecentchanges-summary": "Devuelve un canal de cambios recientes.",
-       "apihelp-feedrecentchanges-param-feedformat": "El formato del canal.",
+       "apihelp-feedrecentchanges-param-feedformat": "El formato del suministro.",
        "apihelp-feedrecentchanges-param-namespace": "Espacio de nombres al cual limitar los resultados.",
        "apihelp-feedrecentchanges-param-invert": "Todos los espacios de nombres menos el que está seleccionado.",
        "apihelp-feedrecentchanges-param-associated": "Incluir el espacio de nombres asociado (discusión o principal).",
        "apihelp-feedrecentchanges-example-simple": "Mostrar los cambios recientes.",
        "apihelp-feedrecentchanges-example-30days": "Mostrar los cambios recientes limitados a 30 días.",
        "apihelp-feedwatchlist-summary": "Devuelve el canal de una lista de seguimiento.",
-       "apihelp-feedwatchlist-param-feedformat": "El formato del canal.",
+       "apihelp-feedwatchlist-param-feedformat": "El formato del suministro.",
        "apihelp-feedwatchlist-param-hours": "Listar las páginas modificadas desde estas horas hasta ahora.",
        "apihelp-feedwatchlist-param-linktosections": "Enlazar directamente a las secciones cambiadas de ser posible.",
        "apihelp-feedwatchlist-example-default": "Mostrar el canal de la lista de seguimiento.",
        "apihelp-query+revisions+base-param-section": "Recuperar solamente el contenido de este número de sección.",
        "apihelp-query+revisions+base-param-contentformat": "Formato de serialización utilizado para <var>$1difftotext</var> y esperado para la salida de contenido.",
        "apihelp-query+search-summary": "Realizar una búsqueda de texto completa.",
-       "apihelp-query+search-param-namespace": "Buscar sólo en estos espacios de nombres.",
+       "apihelp-query+search-param-namespace": "Buscar solo en estos espacios de nombres.",
        "apihelp-query+search-param-what": "Tipo de búsqueda que realizar.",
        "apihelp-query+search-param-info": "Qué metadatos devolver.",
        "apihelp-query+search-param-prop": "Qué propiedades se devolverán:",
        "apihelp-query+watchlistraw-param-limit": "Número de resultados que devolver en cada petición.",
        "apihelp-query+watchlistraw-param-prop": "Qué propiedades adicionales se obtendrán:",
        "apihelp-query+watchlistraw-paramvalue-prop-changed": "Añade la marca de tiempo de la última notificación al usuario sobre la edición.",
-       "apihelp-query+watchlistraw-param-show": "Sólo listar los elementos que cumplen estos criterios.",
+       "apihelp-query+watchlistraw-param-show": "Mostrar solo los elementos que cumplen con estos criterios.",
        "apihelp-query+watchlistraw-param-owner": "Utilizado junto con $1token para acceder a la lista de seguimiento de otro usuario.",
        "apihelp-query+watchlistraw-param-dir": "La dirección en la que se listará.",
        "apihelp-query+watchlistraw-param-fromtitle": "Título (con el prefijo de espacio de nombres) desde el que se empezará a enumerar.",
index efee015..9a16e81 100644 (file)
@@ -29,6 +29,8 @@
        "apihelp-compare-param-toid": "Aldaratzeko bigarren orri IDa.",
        "apihelp-compare-param-torev": "Aldaratzeko bigarren berrikusketa.",
        "apihelp-compare-param-prop": "Hartu beharreko informazio zatiak.",
+       "apihelp-compare-paramvalue-prop-diff": "HTML diff-a",
+       "apihelp-compare-paramvalue-prop-diffsize": "HTML diff-aren tamainia, byte-tan",
        "apihelp-compare-example-1": "1. eta 2. berrikusketen arteko \"diff\"-a sortu.",
        "apihelp-createaccount-summary": "Erabiltzaile kontu berria sortu.",
        "apihelp-createaccount-param-name": "Erabiltzaile izena.",
        "apihelp-feedrecentchanges-example-simple": "Erakutsi aldaketa berriak",
        "apihelp-feedrecentchanges-example-30days": "Erakutsi aldaketa berriak 30 egunez",
        "apihelp-feedwatchlist-param-feedformat": "Produktuaren formatua.",
+       "apihelp-filerevert-summary": "Artxibo bat bertsio zaharrera bueltatu.",
        "apihelp-filerevert-param-comment": "Iruzkina igo.",
        "apihelp-help-example-recursive": "Laguntza guztia orrialde batean.",
        "apihelp-imagerotate-summary": "Irudi bat edo gehiago biratu.",
        "apihelp-imagerotate-param-rotation": "Irudia erloju-orratzen norabidean biratzeko graduak.",
        "apihelp-import-param-summary": "Inportazioaren laburpena.",
        "apihelp-import-param-xml": "XML fitxategia igo da.",
+       "apihelp-import-param-tags": "Aldatu etiketak sarrera aplikatzeko inportatzeko sarreran eta inportatutako orrialdeetan berrikuspena kentzeko.",
        "apihelp-login-param-name": "Erabiltzaile izena.",
        "apihelp-login-param-password": "Pasahitza.",
        "apihelp-login-param-domain": "Domeinua (hautazkoa).",
index 085821c..0d5f224 100644 (file)
        "api-help-param-required": "Dëse Parameter ass obligatoresch.",
        "api-help-datatypes-header": "Datentypen",
        "api-help-param-type-user": "Typ: {{PLURAL:$1|1=Benotzernumm|2=Lëscht vu Benotzernimm}}",
+       "api-help-param-multi-max-simple": "Maximal Zuel vun de Wäerter ass {{PLURAL:$1|$1}}.",
        "api-help-examples": "{{PLURAL:$1|Beispill|Beispiler}}:",
        "api-help-permissions": "{{PLURAL:$1|Autorisatioun|Autorisatiounen}}:",
        "api-help-open-in-apisandbox": "<small>[an der Sandkëscht opmaachen]</small>",
index 38cb6be..30d105b 100644 (file)
@@ -43,7 +43,7 @@ class LinkBatch {
        protected $caller;
 
        /**
-        * @param LinkTarget[] $arr Initial items to be added to the batch
+        * @param Traversable|LinkTarget[] $arr Initial items to be added to the batch
         */
        public function __construct( $arr = [] ) {
                foreach ( $arr as $item ) {
index 01e67f5..913bd38 100644 (file)
@@ -66,6 +66,13 @@ class ChangesListBooleanFilter extends ChangesListFilter {
         */
        protected $queryCallable;
 
+       /**
+        * Value that defined when this filter is considered active
+        *
+        * @var bool $activeValue
+        */
+       protected $activeValue;
+
        /**
         * Create a new filter with the specified configuration.
         *
@@ -90,6 +97,8 @@ class ChangesListBooleanFilter extends ChangesListFilter {
         *     to true.  It does not need to be set if the exact same filter is simply visible
         *     on both.
         * * $filterDefinition['default'] bool Default
+        * * $filterDefinition['activeValue'] bool This filter is considered active when
+        *     its value is equal to its activeValue. Default is true.
         * * $filterDefinition['priority'] int Priority integer.  Higher value means higher
         *     up in the group's filter list.
         * * $filterDefinition['queryCallable'] callable Callable accepting parameters, used
@@ -126,6 +135,12 @@ class ChangesListBooleanFilter extends ChangesListFilter {
                if ( isset( $filterDefinition['queryCallable'] ) ) {
                        $this->queryCallable = $filterDefinition['queryCallable'];
                }
+
+               if ( isset( $filterDefinition['activeValue'] ) ) {
+                       $this->activeValue = $filterDefinition['activeValue'];
+               } else {
+                       $this->activeValue = true;
+               }
        }
 
        /**
@@ -136,7 +151,7 @@ class ChangesListBooleanFilter extends ChangesListFilter {
         */
        public function getDefault( $structuredUI = false ) {
                return $this->isReplacedInStructuredUi && $structuredUI ?
-                       false :
+                       !$this->activeValue :
                        $this->defaultValue;
        }
 
@@ -225,4 +240,17 @@ class ChangesListBooleanFilter extends ChangesListFilter {
                                return $opts[ $sibling->getName() ];
                        } );
        }
+
+       /**
+        * @param FormOptions $opts Query parameters merged with defaults
+        * @param bool $isStructuredUI Whether the structured UI is currently enabled
+        * @return bool Whether this filter should be considered active
+        */
+       public function isActive( FormOptions $opts, $isStructuredUI ) {
+               if ( $this->isReplacedInStructuredUi && $isStructuredUI ) {
+                       return false;
+               }
+
+               return $opts[ $this->getName() ] === $this->activeValue;
+       }
 }
index f123363..588f602 100644 (file)
  * temporary:       not stored in the database
  *      notificationtimestamp
  *      numberofWatchingusers
+ *
+ * @todo Deprecate access to mAttribs (direct or via getAttributes). Right now
+ *  we're having to include both rc_comment and rc_comment_text/rc_comment_data
+ *  so random crap works right.
  */
 class RecentChange {
        // Constants for the rc_source field.  Extensions may also have
@@ -199,6 +203,8 @@ class RecentChange {
        /**
         * Return the list of recentchanges fields that should be selected to create
         * a new recentchanges object.
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        public static function selectFields() {
@@ -209,7 +215,6 @@ class RecentChange {
                        'rc_user_text',
                        'rc_namespace',
                        'rc_title',
-                       'rc_comment',
                        'rc_minor',
                        'rc_bot',
                        'rc_new',
@@ -227,7 +232,7 @@ class RecentChange {
                        'rc_log_type',
                        'rc_log_action',
                        'rc_params',
-               ];
+               ] + CommentStore::newKey( 'rc_comment' )->getFields();
        }
 
        # Accessors
@@ -322,8 +327,14 @@ class RecentChange {
                        unset( $this->mAttribs['rc_cur_id'] );
                }
 
+               # Convert mAttribs['rc_comment'] for CommentStore
+               $row = $this->mAttribs;
+               $comment = $row['rc_comment'];
+               unset( $row['rc_comment'], $row['rc_comment_text'], $row['rc_comment_data'] );
+               $row += CommentStore::newKey( 'rc_comment' )->insert( $dbw, $comment );
+
                # Insert new row
-               $dbw->insert( 'recentchanges', $this->mAttribs, __METHOD__ );
+               $dbw->insert( 'recentchanges', $row, __METHOD__ );
 
                # Set the ID
                $this->mAttribs['rc_id'] = $dbw->insertId();
@@ -586,7 +597,9 @@ class RecentChange {
                        'rc_cur_id' => $title->getArticleID(),
                        'rc_user' => $user->getId(),
                        'rc_user_text' => $user->getName(),
-                       'rc_comment' => $comment,
+                       'rc_comment' => &$comment,
+                       'rc_comment_text' => &$comment,
+                       'rc_comment_data' => null,
                        'rc_this_oldid' => $newId,
                        'rc_last_oldid' => $oldId,
                        'rc_bot' => $bot ? 1 : 0,
@@ -659,7 +672,9 @@ class RecentChange {
                        'rc_cur_id' => $title->getArticleID(),
                        'rc_user' => $user->getId(),
                        'rc_user_text' => $user->getName(),
-                       'rc_comment' => $comment,
+                       'rc_comment' => &$comment,
+                       'rc_comment_text' => &$comment,
+                       'rc_comment_data' => null,
                        'rc_this_oldid' => $newId,
                        'rc_last_oldid' => 0,
                        'rc_bot' => $bot ? 1 : 0,
@@ -789,7 +804,9 @@ class RecentChange {
                        'rc_cur_id' => $target->getArticleID(),
                        'rc_user' => $user->getId(),
                        'rc_user_text' => $user->getName(),
-                       'rc_comment' => $logComment,
+                       'rc_comment' => &$logComment,
+                       'rc_comment_text' => &$logComment,
+                       'rc_comment_data' => null,
                        'rc_this_oldid' => $revId,
                        'rc_last_oldid' => 0,
                        'rc_bot' => $user->isAllowed( 'bot' ) ? (int)$wgRequest->getBool( 'bot', true ) : 0,
@@ -862,7 +879,9 @@ class RecentChange {
                        'rc_cur_id' => $pageTitle->getArticleID(),
                        'rc_user' => $user ? $user->getId() : 0,
                        'rc_user_text' => $user ? $user->getName() : '',
-                       'rc_comment' => $comment,
+                       'rc_comment' => &$comment,
+                       'rc_comment_text' => &$comment,
+                       'rc_comment_data' => null,
                        'rc_this_oldid' => $newRevId,
                        'rc_last_oldid' => $oldRevId,
                        'rc_bot' => $bot ? 1 : 0,
@@ -922,6 +941,13 @@ class RecentChange {
                                $this->mAttribs['rc_ip'] = substr( $this->mAttribs['rc_ip'], 0, $n );
                        }
                }
+
+               $comment = CommentStore::newKey( 'rc_comment' )
+                       // Legacy because $row probably came from self::selectFields()
+                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
+               $this->mAttribs['rc_comment'] = &$comment;
+               $this->mAttribs['rc_comment_text'] = &$comment;
+               $this->mAttribs['rc_comment_data'] = null;
        }
 
        /**
@@ -931,6 +957,9 @@ class RecentChange {
         * @return mixed
         */
        public function getAttribute( $name ) {
+               if ( $name === 'rc_comment' ) {
+                       return CommentStore::newKey( 'rc_comment' )->getComment( $this->mAttribs, true )->text;
+               }
                return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : null;
        }
 
index d7dc45a..0ec21cb 100644 (file)
@@ -228,7 +228,7 @@ class EtcdConfig implements Config, LoggerAwareInterface {
                // Retrieve all the values under the MediaWiki config directory
                list( $rcode, $rdesc, /* $rhdrs */, $rbody, $rerr ) = $this->http->run( [
                        'method' => 'GET',
-                       'url' => "{$this->protocol}://{$address}/v2/keys/{$this->directory}/",
+                       'url' => "{$this->protocol}://{$address}/v2/keys/{$this->directory}/?recursive=true",
                        'headers' => [ 'content-type' => 'application/json' ]
                ] );
 
@@ -240,28 +240,65 @@ class EtcdConfig implements Config, LoggerAwareInterface {
                                empty( $terminalCodes[$rcode] )
                        ];
                }
+               try {
+                       return [ $this->parseResponse( $rbody ), null, false ];
+               } catch ( EtcdConfigParseError $e ) {
+                       return [ null, $e->getMessage(), false ];
+               }
+       }
 
+       /**
+        * Parse a response body, throwing EtcdConfigParseError if there is a validation error
+        *
+        * @param string $rbody
+        * @return array
+        */
+       protected function parseResponse( $rbody ) {
                $info = json_decode( $rbody, true );
-               if ( $info === null || !isset( $info['node']['nodes'] ) ) {
-                       return [ null, "Unexpected JSON response; missing 'nodes' list.", false ];
+               if ( $info === null ) {
+                       throw new EtcdConfigParseError( "Error unserializing JSON response." );
+               }
+               if ( !isset( $info['node'] ) || !is_array( $info['node'] ) ) {
+                       throw new EtcdConfigParseError(
+                               "Unexpected JSON response: Missing or invalid node at top level." );
                }
-
                $config = [];
-               foreach ( $info['node']['nodes'] as $node ) {
+               $this->parseDirectory( '', $info['node'], $config );
+               return $config;
+       }
+
+       /**
+        * Recursively parse a directory node and populate the array passed by
+        * reference, throwing EtcdConfigParseError if there is a validation error
+        *
+        * @param string $dirName The relative directory name
+        * @param array $dirNode The decoded directory node
+        * @param array &$config The output array
+        */
+       protected function parseDirectory( $dirName, $dirNode, &$config ) {
+               if ( !isset( $dirNode['nodes'] ) ) {
+                       throw new EtcdConfigParseError(
+                               "Unexpected JSON response in dir '$dirName'; missing 'nodes' list." );
+               }
+               if ( !is_array( $dirNode['nodes'] ) ) {
+                       throw new EtcdConfigParseError(
+                               "Unexpected JSON response in dir '$dirName'; 'nodes' is not an array." );
+               }
+
+               foreach ( $dirNode['nodes'] as $node ) {
+                       $baseName = basename( $node['key'] );
+                       $fullName = $dirName === '' ? $baseName : "$dirName/$baseName";
                        if ( !empty( $node['dir'] ) ) {
-                               continue; // skip directories
-                       }
+                               $this->parseDirectory( $fullName, $node, $config );
+                       } else {
+                               $value = $this->unserialize( $node['value'] );
+                               if ( !is_array( $value ) || !array_key_exists( 'val', $value ) ) {
+                                       throw new EtcdConfigParseError( "Failed to parse value for '$fullName'." );
+                               }
 
-                       $name = basename( $node['key'] );
-                       $value = $this->unserialize( $node['value'] );
-                       if ( !is_array( $value ) || !array_key_exists( 'val', $value ) ) {
-                               return [ null, "Failed to parse value for '$name'.", false ];
+                               $config[$fullName] = $value['val'];
                        }
-
-                       $config[$name] = $value['val'];
                }
-
-               return [ $config, null, false ];
        }
 
        /**
diff --git a/includes/config/EtcdConfigParseError.php b/includes/config/EtcdConfigParseError.php
new file mode 100644 (file)
index 0000000..cab90a8
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+
+class EtcdConfigParseError extends Exception {
+}
index 556fe75..e2feb1f 100644 (file)
@@ -37,9 +37,6 @@ class DatabaseOracle extends Database {
        /** @var int The number of rows affected as an integer */
        protected $mAffectedRows;
 
-       /** @var int */
-       private $mInsertId = null;
-
        /** @var bool */
        private $ignoreDupValOnIndex = false;
 
@@ -319,12 +316,10 @@ class DatabaseOracle extends Database {
                return oci_field_name( $stmt, $n );
        }
 
-       /**
-        * This must be called after nextSequenceVal
-        * @return null|int
-        */
        function insertId() {
-               return $this->mInsertId;
+               $res = $this->query( "SELECT lastval_pkg.getLastval FROM dual" );
+               $row = $this->fetchRow( $res );
+               return is_null( $row[0] ) ? null : (int)$row[0];
        }
 
        /**
@@ -649,20 +644,6 @@ class DatabaseOracle extends Database {
                return preg_replace( '/.*\.(.*)/', '$1', $name );
        }
 
-       /**
-        * Return the next in a sequence, save the value for retrieval via insertId()
-        *
-        * @param string $seqName
-        * @return null|int
-        */
-       function nextSequenceValue( $seqName ) {
-               $res = $this->query( "SELECT $seqName.nextval FROM dual" );
-               $row = $this->fetchRow( $res );
-               $this->mInsertId = $row[0];
-
-               return $this->mInsertId;
-       }
-
        /**
         * Return sequence_name if table has a sequence
         *
index 470086a..7fafc0e 100644 (file)
@@ -49,11 +49,12 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
        /**
         * Create an update object from an array of Title objects, or a TitleArray object
         *
-        * @param Traversable|array $titles
+        * @param Traversable|Title[] $titles
         * @param string[] $urlArr
         * @return CdnCacheUpdate
         */
        public static function newFromTitles( $titles, $urlArr = [] ) {
+               ( new LinkBatch( $titles ) )->execute();
                /** @var Title $title */
                foreach ( $titles as $title ) {
                        $urlArr = array_merge( $urlArr, $title->getCdnUrls() );
index e0ebaa2..6e2a5a4 100644 (file)
@@ -260,7 +260,7 @@ class WikiExporter {
        protected function dumpFrom( $cond = '', $orderRevs = false ) {
                # For logging dumps...
                if ( $this->history & self::LOGS ) {
-                       $where = [ 'user_id = log_user' ];
+                       $where = [];
                        # Hide private logs
                        $hideLogs = LogEventsList::getExcludeClause( $this->db );
                        if ( $hideLogs ) {
@@ -277,12 +277,16 @@ class WikiExporter {
                                $prev = $this->db->bufferResults( false );
                        }
                        $result = null; // Assuring $result is not undefined, if exception occurs early
+
+                       $commentQuery = CommentStore::newKey( 'log_comment' )->getJoin();
+
                        try {
-                               $result = $this->db->select( [ 'logging', 'user' ],
-                                       [ "{$logging}.*", 'user_name' ], // grab the user name
+                               $result = $this->db->select( [ 'logging', 'user' ] + $commentQuery['tables'],
+                                       [ "{$logging}.*", 'user_name' ] + $commentQuery['fields'], // grab the user name
                                        $where,
                                        __METHOD__,
-                                       [ 'ORDER BY' => 'log_id', 'USE INDEX' => [ 'logging' => 'PRIMARY' ] ]
+                                       [ 'ORDER BY' => 'log_id', 'USE INDEX' => [ 'logging' => 'PRIMARY' ] ],
+                                       [ 'user' => [ 'JOIN', 'user_id = log_user' ] ] + $commentQuery['joins']
                                );
                                $this->outputLogStream( $result );
                                if ( $this->buffer == self::STREAM ) {
@@ -395,8 +399,17 @@ class WikiExporter {
                                Hooks::run( 'ModifyExportQuery',
                                                [ $this->db, &$tables, &$cond, &$opts, &$join ] );
 
+                               $commentQuery = CommentStore::newKey( 'rev_comment' )->getJoin();
+
                                # Do the query!
-                               $result = $this->db->select( $tables, '*', $cond, __METHOD__, $opts, $join );
+                               $result = $this->db->select(
+                                       $tables + $commentQuery['tables'],
+                                       [ '*' ] + $commentQuery['fields'],
+                                       $cond,
+                                       __METHOD__,
+                                       $opts,
+                                       $join + $commentQuery['joins']
+                               );
                                # Output dump results
                                $this->outputPageStream( $result );
 
index 943408c..990f16d 100644 (file)
@@ -218,8 +218,11 @@ class XmlDumpWriter {
                }
                if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_COMMENT ) ) {
                        $out .= "      " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
-               } elseif ( $row->rev_comment != '' ) {
-                       $out .= "      " . Xml::elementClean( 'comment', [], strval( $row->rev_comment ) ) . "\n";
+               } else {
+                       $comment = CommentStore::newKey( 'rev_comment' )->getComment( $row )->text;
+                       if ( $comment != '' ) {
+                               $out .= "      " . Xml::elementClean( 'comment', [], strval( $comment ) ) . "\n";
+                       }
                }
 
                if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model ) ) {
@@ -299,8 +302,11 @@ class XmlDumpWriter {
 
                if ( $row->log_deleted & LogPage::DELETED_COMMENT ) {
                        $out .= "    " . Xml::element( 'comment', [ 'deleted' => 'deleted' ] ) . "\n";
-               } elseif ( $row->log_comment != '' ) {
-                       $out .= "    " . Xml::elementClean( 'comment', null, strval( $row->log_comment ) ) . "\n";
+               } else {
+                       $comment = CommentStore::newKey( 'log_comment' )->getComment( $row )->text;
+                       if ( $comment != '' ) {
+                               $out .= "    " . Xml::elementClean( 'comment', null, strval( $comment ) ) . "\n";
+                       }
                }
 
                $out .= "    " . Xml::element( 'type', null, strval( $row->log_type ) ) . "\n";
index 6984d48..758fb4b 100644 (file)
@@ -215,6 +215,8 @@ class ArchivedFile {
 
        /**
         * Fields in the filearchive table
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        static function selectFields() {
@@ -232,14 +234,13 @@ class ArchivedFile {
                        'fa_media_type',
                        'fa_major_mime',
                        'fa_minor_mime',
-                       'fa_description',
                        'fa_user',
                        'fa_user_text',
                        'fa_timestamp',
                        'fa_deleted',
                        'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
                        'fa_sha1',
-               ];
+               ] + CommentStore::newKey( 'fa_description' )->getFields();
        }
 
        /**
@@ -261,7 +262,9 @@ class ArchivedFile {
                $this->metadata = $row->fa_metadata;
                $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
                $this->media_type = $row->fa_media_type;
-               $this->description = $row->fa_description;
+               $this->description = CommentStore::newKey( 'fa_description' )
+                       // Legacy because $row probably came from self::selectFields()
+                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row )->text;
                $this->user = $row->fa_user;
                $this->user_text = $row->fa_user_text;
                $this->timestamp = $row->fa_timestamp;
index 33177d3..904c932 100644 (file)
@@ -193,6 +193,8 @@ class LocalFile extends File {
 
        /**
         * Fields in the image table
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        static function selectFields() {
@@ -206,12 +208,11 @@ class LocalFile extends File {
                        'img_media_type',
                        'img_major_mime',
                        'img_minor_mime',
-                       'img_description',
                        'img_user',
                        'img_user_text',
                        'img_timestamp',
                        'img_sha1',
-               ];
+               ] + CommentStore::newKey( 'img_description' )->getFields();
        }
 
        /**
@@ -1299,6 +1300,8 @@ class LocalFile extends File {
        function recordUpload2(
                $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = []
        ) {
+               global $wgCommentTableSchemaMigrationStage;
+
                if ( is_null( $user ) ) {
                        global $wgUser;
                        $user = $wgUser;
@@ -1334,6 +1337,9 @@ class LocalFile extends File {
                # Test to see if the row exists using INSERT IGNORE
                # This avoids race conditions by locking the row until the commit, and also
                # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
+               $commentStore = new CommentStore( 'img_description' );
+               list( $commentFields, $commentCallback ) =
+                       $commentStore->insertWithTempTable( $dbw, $comment );
                $dbw->insert( 'image',
                        [
                                'img_name' => $this->getName(),
@@ -1345,17 +1351,16 @@ class LocalFile extends File {
                                'img_major_mime' => $this->major_mime,
                                'img_minor_mime' => $this->minor_mime,
                                'img_timestamp' => $timestamp,
-                               'img_description' => $comment,
                                'img_user' => $user->getId(),
                                'img_user_text' => $user->getName(),
                                'img_metadata' => $dbw->encodeBlob( $this->metadata ),
                                'img_sha1' => $this->sha1
-                       ],
+                       ] + $commentFields,
                        __METHOD__,
                        'IGNORE'
                );
-
                $reupload = ( $dbw->affectedRows() == 0 );
+
                if ( $reupload ) {
                        if ( $allowTimeKludge ) {
                                # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
@@ -1376,33 +1381,65 @@ class LocalFile extends File {
                                }
                        }
 
+                       $tables = [ 'image' ];
+                       $fields = [
+                               'oi_name' => 'img_name',
+                               'oi_archive_name' => $dbw->addQuotes( $oldver ),
+                               'oi_size' => 'img_size',
+                               'oi_width' => 'img_width',
+                               'oi_height' => 'img_height',
+                               'oi_bits' => 'img_bits',
+                               'oi_timestamp' => 'img_timestamp',
+                               'oi_user' => 'img_user',
+                               'oi_user_text' => 'img_user_text',
+                               'oi_metadata' => 'img_metadata',
+                               'oi_media_type' => 'img_media_type',
+                               'oi_major_mime' => 'img_major_mime',
+                               'oi_minor_mime' => 'img_minor_mime',
+                               'oi_sha1' => 'img_sha1',
+                       ];
+                       $joins = [];
+
+                       if ( $wgCommentTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               $fields['oi_description'] = 'img_description';
+                       }
+                       if ( $wgCommentTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               $tables[] = 'image_comment_temp';
+                               $fields['oi_description_id'] = 'imgcomment_description_id';
+                               $joins['image_comment_temp'] = [
+                                       $wgCommentTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                                       [ 'imgcomment_name = img_name' ]
+                               ];
+                       }
+
+                       if ( $wgCommentTableSchemaMigrationStage !== MIGRATION_OLD &&
+                               $wgCommentTableSchemaMigrationStage !== MIGRATION_NEW
+                       ) {
+                               // Upgrade any rows that are still old-style. Otherwise an upgrade
+                               // might be missed if a deletion happens while the migration script
+                               // is running.
+                               $res = $dbw->select(
+                                       [ 'image', 'image_comment_temp' ],
+                                       [ 'img_name', 'img_description' ],
+                                       [ 'img_name' => $this->getName(), 'imgcomment_name' => null ],
+                                       __METHOD__,
+                                       [],
+                                       [ 'image_comment_temp' => [ 'LEFT JOIN', [ 'imgcomment_name = img_name' ] ] ]
+                               );
+                               foreach ( $res as $row ) {
+                                       list( , $callback ) = $commentStore->insertWithTempTable( $dbw, $row->img_description );
+                                       $callback( $row->img_name );
+                               }
+                       }
+
                        # (T36993) Note: $oldver can be empty here, if the previous
                        # version of the file was broken. Allow registration of the new
                        # version to continue anyway, because that's better than having
                        # an image that's not fixable by user operations.
                        # Collision, this is an update of a file
                        # Insert previous contents into oldimage
-                       $dbw->insertSelect( 'oldimage', 'image',
-                               [
-                                       'oi_name' => 'img_name',
-                                       'oi_archive_name' => $dbw->addQuotes( $oldver ),
-                                       'oi_size' => 'img_size',
-                                       'oi_width' => 'img_width',
-                                       'oi_height' => 'img_height',
-                                       'oi_bits' => 'img_bits',
-                                       'oi_timestamp' => 'img_timestamp',
-                                       'oi_description' => 'img_description',
-                                       'oi_user' => 'img_user',
-                                       'oi_user_text' => 'img_user_text',
-                                       'oi_metadata' => 'img_metadata',
-                                       'oi_media_type' => 'img_media_type',
-                                       'oi_major_mime' => 'img_major_mime',
-                                       'oi_minor_mime' => 'img_minor_mime',
-                                       'oi_sha1' => 'img_sha1'
-                               ],
-                               [ 'img_name' => $this->getName() ],
-                               __METHOD__
-                       );
+                       $dbw->insertSelect( 'oldimage', $tables, $fields,
+                               [ 'img_name' => $this->getName() ], __METHOD__, [], [], $joins );
 
                        # Update the current image row
                        $dbw->update( 'image',
@@ -1415,16 +1452,20 @@ class LocalFile extends File {
                                        'img_major_mime' => $this->major_mime,
                                        'img_minor_mime' => $this->minor_mime,
                                        'img_timestamp' => $timestamp,
-                                       'img_description' => $comment,
                                        'img_user' => $user->getId(),
                                        'img_user_text' => $user->getName(),
                                        'img_metadata' => $dbw->encodeBlob( $this->metadata ),
                                        'img_sha1' => $this->sha1
-                               ],
+                               ] + $commentFields,
                                [ 'img_name' => $this->getName() ],
                                __METHOD__
                        );
+                       if ( $wgCommentTableSchemaMigrationStage > MIGRATION_OLD ) {
+                               // So $commentCallback can insert the new row
+                               $dbw->delete( 'image_comment_temp', [ 'imgcomment_name' => $this->getName() ], __METHOD__ );
+                       }
                }
+               $commentCallback( $this->getName() );
 
                $descTitle = $this->getTitle();
                $descId = $descTitle->getArticleID();
@@ -2255,8 +2296,16 @@ class LocalFileDeleteBatch {
        }
 
        protected function doDBInserts() {
+               global $wgCommentTableSchemaMigrationStage;
+
                $now = time();
                $dbw = $this->file->repo->getMasterDB();
+
+               $commentStoreImgDesc = new CommentStore( 'img_description' );
+               $commentStoreOiDesc = new CommentStore( 'oi_description' );
+               $commentStoreFaDesc = new CommentStore( 'fa_description' );
+               $commentStoreFaReason = new CommentStore( 'fa_deleted_reason' );
+
                $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
                $encUserId = $dbw->addQuotes( $this->user->getId() );
                $encReason = $dbw->addQuotes( $this->reason );
@@ -2274,39 +2323,70 @@ class LocalFileDeleteBatch {
                }
 
                if ( $deleteCurrent ) {
-                       $dbw->insertSelect(
-                               'filearchive',
-                               'image',
-                               [
-                                       'fa_storage_group' => $encGroup,
-                                       'fa_storage_key' => $dbw->conditional(
-                                               [ 'img_sha1' => '' ],
-                                               $dbw->addQuotes( '' ),
-                                               $dbw->buildConcat( [ "img_sha1", $encExt ] )
-                                       ),
-                                       'fa_deleted_user' => $encUserId,
-                                       'fa_deleted_timestamp' => $encTimestamp,
-                                       'fa_deleted_reason' => $encReason,
-                                       'fa_deleted' => $this->suppress ? $bitfield : 0,
-                                       'fa_name' => 'img_name',
-                                       'fa_archive_name' => 'NULL',
-                                       'fa_size' => 'img_size',
-                                       'fa_width' => 'img_width',
-                                       'fa_height' => 'img_height',
-                                       'fa_metadata' => 'img_metadata',
-                                       'fa_bits' => 'img_bits',
-                                       'fa_media_type' => 'img_media_type',
-                                       'fa_major_mime' => 'img_major_mime',
-                                       'fa_minor_mime' => 'img_minor_mime',
-                                       'fa_description' => 'img_description',
-                                       'fa_user' => 'img_user',
-                                       'fa_user_text' => 'img_user_text',
-                                       'fa_timestamp' => 'img_timestamp',
-                                       'fa_sha1' => 'img_sha1'
-                               ],
-                               [ 'img_name' => $this->file->getName() ],
-                               __METHOD__
-                       );
+                       $tables = [ 'image' ];
+                       $fields = [
+                               'fa_storage_group' => $encGroup,
+                               'fa_storage_key' => $dbw->conditional(
+                                       [ 'img_sha1' => '' ],
+                                       $dbw->addQuotes( '' ),
+                                       $dbw->buildConcat( [ "img_sha1", $encExt ] )
+                               ),
+                               'fa_deleted_user' => $encUserId,
+                               'fa_deleted_timestamp' => $encTimestamp,
+                               'fa_deleted' => $this->suppress ? $bitfield : 0,
+                               'fa_name' => 'img_name',
+                               'fa_archive_name' => 'NULL',
+                               'fa_size' => 'img_size',
+                               'fa_width' => 'img_width',
+                               'fa_height' => 'img_height',
+                               'fa_metadata' => 'img_metadata',
+                               'fa_bits' => 'img_bits',
+                               'fa_media_type' => 'img_media_type',
+                               'fa_major_mime' => 'img_major_mime',
+                               'fa_minor_mime' => 'img_minor_mime',
+                               'fa_user' => 'img_user',
+                               'fa_user_text' => 'img_user_text',
+                               'fa_timestamp' => 'img_timestamp',
+                               'fa_sha1' => 'img_sha1'
+                       ];
+                       $joins = [];
+
+                       $fields += $commentStoreFaReason->insert( $dbw, $encReason );
+
+                       if ( $wgCommentTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               $fields['fa_description'] = 'img_description';
+                       }
+                       if ( $wgCommentTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               $tables[] = 'image_comment_temp';
+                               $fields['fa_description_id'] = 'imgcomment_description_id';
+                               $joins['image_comment_temp'] = [
+                                       $wgCommentTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                                       [ 'imgcomment_name = img_name' ]
+                               ];
+                       }
+
+                       if ( $wgCommentTableSchemaMigrationStage !== MIGRATION_OLD &&
+                               $wgCommentTableSchemaMigrationStage !== MIGRATION_NEW
+                       ) {
+                               // Upgrade any rows that are still old-style. Otherwise an upgrade
+                               // might be missed if a deletion happens while the migration script
+                               // is running.
+                               $res = $dbw->select(
+                                       [ 'image', 'image_comment_temp' ],
+                                       [ 'img_name', 'img_description' ],
+                                       [ 'img_name' => $this->file->getName(), 'imgcomment_name' => null ],
+                                       __METHOD__,
+                                       [],
+                                       [ 'image_comment_temp' => [ 'LEFT JOIN', [ 'imgcomment_name = img_name' ] ] ]
+                               );
+                               foreach ( $res as $row ) {
+                                       list( , $callback ) = $commentStoreImgDesc->insertWithTempTable( $dbw, $row->img_description );
+                                       $callback( $row->img_name );
+                               }
+                       }
+
+                       $dbw->insertSelect( 'filearchive', $tables, $fields,
+                               [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
                }
 
                if ( count( $oldRels ) ) {
@@ -2321,34 +2401,38 @@ class LocalFileDeleteBatch {
                                [ 'FOR UPDATE' ]
                        );
                        $rowsInsert = [];
-                       foreach ( $res as $row ) {
-                               $rowsInsert[] = [
-                                       // Deletion-specific fields
-                                       'fa_storage_group' => 'deleted',
-                                       'fa_storage_key' => ( $row->oi_sha1 === '' )
+                       if ( $res->numRows() ) {
+                               $reason = $commentStoreFaReason->createComment( $dbw, $this->reason );
+                               foreach ( $res as $row ) {
+                                       // Legacy from OldLocalFile::selectFields() just above
+                                       $comment = $commentStoreOiDesc->getCommentLegacy( $dbw, $row );
+                                       $rowsInsert[] = [
+                                               // Deletion-specific fields
+                                               'fa_storage_group' => 'deleted',
+                                               'fa_storage_key' => ( $row->oi_sha1 === '' )
                                                ? ''
                                                : "{$row->oi_sha1}{$dotExt}",
-                                       'fa_deleted_user' => $this->user->getId(),
-                                       'fa_deleted_timestamp' => $dbw->timestamp( $now ),
-                                       'fa_deleted_reason' => $this->reason,
-                                       // Counterpart fields
-                                       'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
-                                       'fa_name' => $row->oi_name,
-                                       'fa_archive_name' => $row->oi_archive_name,
-                                       'fa_size' => $row->oi_size,
-                                       'fa_width' => $row->oi_width,
-                                       'fa_height' => $row->oi_height,
-                                       'fa_metadata' => $row->oi_metadata,
-                                       'fa_bits' => $row->oi_bits,
-                                       'fa_media_type' => $row->oi_media_type,
-                                       'fa_major_mime' => $row->oi_major_mime,
-                                       'fa_minor_mime' => $row->oi_minor_mime,
-                                       'fa_description' => $row->oi_description,
-                                       'fa_user' => $row->oi_user,
-                                       'fa_user_text' => $row->oi_user_text,
-                                       'fa_timestamp' => $row->oi_timestamp,
-                                       'fa_sha1' => $row->oi_sha1
-                               ];
+                                               'fa_deleted_user' => $this->user->getId(),
+                                               'fa_deleted_timestamp' => $dbw->timestamp( $now ),
+                                               // Counterpart fields
+                                               'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
+                                               'fa_name' => $row->oi_name,
+                                               'fa_archive_name' => $row->oi_archive_name,
+                                               'fa_size' => $row->oi_size,
+                                               'fa_width' => $row->oi_width,
+                                               'fa_height' => $row->oi_height,
+                                               'fa_metadata' => $row->oi_metadata,
+                                               'fa_bits' => $row->oi_bits,
+                                               'fa_media_type' => $row->oi_media_type,
+                                               'fa_major_mime' => $row->oi_major_mime,
+                                               'fa_minor_mime' => $row->oi_minor_mime,
+                                               'fa_user' => $row->oi_user,
+                                               'fa_user_text' => $row->oi_user_text,
+                                               'fa_timestamp' => $row->oi_timestamp,
+                                               'fa_sha1' => $row->oi_sha1
+                                       ] + $commentStoreFaReason->insert( $dbw, $reason )
+                                       + $commentStoreFaDesc->insert( $dbw, $comment );
+                               }
                        }
 
                        $dbw->insert( 'filearchive', $rowsInsert, __METHOD__ );
@@ -2356,6 +2440,8 @@ class LocalFileDeleteBatch {
        }
 
        function doDBDeletes() {
+               global $wgUpdateCompatibleMetadata;
+
                $dbw = $this->file->repo->getMasterDB();
                list( $oldRels, $deleteCurrent ) = $this->getOldRels();
 
@@ -2369,6 +2455,11 @@ class LocalFileDeleteBatch {
 
                if ( $deleteCurrent ) {
                        $dbw->delete( 'image', [ 'img_name' => $this->file->getName() ], __METHOD__ );
+                       if ( $wgUpdateCompatibleMetadata > MIGRATION_OLD ) {
+                               $dbw->delete(
+                                       'image_comment_temp', [ 'imgcomment_name' => $this->file->getName() ], __METHOD__
+                               );
+                       }
                }
        }
 
@@ -2537,6 +2628,11 @@ class LocalFileRestoreBatch {
                $lockOwnsTrx = $this->file->lock();
 
                $dbw = $this->file->repo->getMasterDB();
+
+               $commentStoreImgDesc = new CommentStore( 'img_description' );
+               $commentStoreOiDesc = new CommentStore( 'oi_description' );
+               $commentStoreFaDesc = new CommentStore( 'fa_description' );
+
                $status = $this->file->repo->newGood();
 
                $exists = (bool)$dbw->selectField( 'image', '1',
@@ -2621,9 +2717,13 @@ class LocalFileRestoreBatch {
                                ];
                        }
 
+                       // Legacy from ArchivedFile::selectFields() just above
+                       $comment = $commentStoreFaDesc->getCommentLegacy( $dbw, $row );
                        if ( $first && !$exists ) {
                                // This revision will be published as the new current version
                                $destRel = $this->file->getRel();
+                               list( $commentFields, $commentCallback ) =
+                                       $commentStoreImgDesc->insertWithTempTable( $dbw, $comment );
                                $insertCurrent = [
                                        'img_name' => $row->fa_name,
                                        'img_size' => $row->fa_size,
@@ -2634,12 +2734,11 @@ class LocalFileRestoreBatch {
                                        'img_media_type' => $props['media_type'],
                                        'img_major_mime' => $props['major_mime'],
                                        'img_minor_mime' => $props['minor_mime'],
-                                       'img_description' => $row->fa_description,
                                        'img_user' => $row->fa_user,
                                        'img_user_text' => $row->fa_user_text,
                                        'img_timestamp' => $row->fa_timestamp,
                                        'img_sha1' => $sha1
-                               ];
+                               ] + $commentFields;
 
                                // The live (current) version cannot be hidden!
                                if ( !$this->unsuppress && $row->fa_deleted ) {
@@ -2671,7 +2770,6 @@ class LocalFileRestoreBatch {
                                        'oi_width' => $row->fa_width,
                                        'oi_height' => $row->fa_height,
                                        'oi_bits' => $row->fa_bits,
-                                       'oi_description' => $row->fa_description,
                                        'oi_user' => $row->fa_user,
                                        'oi_user_text' => $row->fa_user_text,
                                        'oi_timestamp' => $row->fa_timestamp,
@@ -2680,7 +2778,8 @@ class LocalFileRestoreBatch {
                                        'oi_major_mime' => $props['major_mime'],
                                        'oi_minor_mime' => $props['minor_mime'],
                                        'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
-                                       'oi_sha1' => $sha1 ];
+                                       'oi_sha1' => $sha1
+                               ] + $commentStoreOiDesc->insert( $dbw, $comment );
                        }
 
                        $deleteIds[] = $row->fa_id;
@@ -2738,6 +2837,7 @@ class LocalFileRestoreBatch {
                // This is not ideal, which is why it's important to lock the image row.
                if ( $insertCurrent ) {
                        $dbw->insert( 'image', $insertCurrent, __METHOD__ );
+                       $commentCallback( $insertCurrent['img_name'] );
                }
 
                if ( $insertBatch ) {
index dfaae73..b46e1e4 100644 (file)
@@ -103,6 +103,8 @@ class OldLocalFile extends LocalFile {
 
        /**
         * Fields in the oldimage table
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        static function selectFields() {
@@ -117,13 +119,12 @@ class OldLocalFile extends LocalFile {
                        'oi_media_type',
                        'oi_major_mime',
                        'oi_minor_mime',
-                       'oi_description',
                        'oi_user',
                        'oi_user_text',
                        'oi_timestamp',
                        'oi_deleted',
                        'oi_sha1',
-               ];
+               ] + CommentStore::newKey( 'oi_description' )->getFields();
        }
 
        /**
@@ -367,6 +368,7 @@ class OldLocalFile extends LocalFile {
                        return false;
                }
 
+               $commentFields = CommentStore::newKey( 'oi_description' )->insert( $dbw, $comment );
                $dbw->insert( 'oldimage',
                        [
                                'oi_name' => $this->getName(),
@@ -376,7 +378,6 @@ class OldLocalFile extends LocalFile {
                                'oi_height' => intval( $props['height'] ),
                                'oi_bits' => $props['bits'],
                                'oi_timestamp' => $dbw->timestamp( $timestamp ),
-                               'oi_description' => $comment,
                                'oi_user' => $user->getId(),
                                'oi_user_text' => $user->getName(),
                                'oi_metadata' => $props['metadata'],
@@ -384,7 +385,7 @@ class OldLocalFile extends LocalFile {
                                'oi_major_mime' => $props['major_mime'],
                                'oi_minor_mime' => $props['minor_mime'],
                                'oi_sha1' => $props['sha1'],
-                       ], __METHOD__
+                       ] + $commentFields, __METHOD__
                );
 
                return true;
index 2099709..9066079 100644 (file)
@@ -813,7 +813,7 @@ class WikiImporter {
                $this->debug( "Enter revision handler" );
                $revisionInfo = [];
 
-               $normalFields = [ 'id', 'timestamp', 'comment', 'minor', 'model', 'format', 'text' ];
+               $normalFields = [ 'id', 'timestamp', 'comment', 'minor', 'model', 'format', 'text', 'sha1' ];
 
                $skip = false;
 
@@ -916,6 +916,9 @@ class WikiImporter {
                } else {
                        $revision->setUsername( 'Unknown user' );
                }
+               if ( isset( $revisionInfo['sha1'] ) ) {
+                       $revision->setSha1Base36( $revisionInfo['sha1'] );
+               }
                $revision->setNoUpdates( $this->mNoUpdates );
 
                return $this->revisionCallback( $revision );
index f6becb9..93a92ef 100644 (file)
@@ -607,11 +607,12 @@ class WikiRevision {
                        $pageId = $page->getId();
                        $created = false;
 
+                       // Note: sha1 has been in XML dumps since 2012. If you have an
+                       // older dump, the duplicate detection here won't work.
                        $prior = $dbw->selectField( 'revision', '1',
                                [ 'rev_page' => $pageId,
                                        'rev_timestamp' => $dbw->timestamp( $this->timestamp ),
-                                       'rev_user_text' => $userText,
-                                       'rev_comment' => $this->getComment() ],
+                                       'rev_sha1' => $this->sha1base36 ],
                                __METHOD__
                        );
                        if ( $prior ) {
@@ -708,7 +709,6 @@ class WikiRevision {
                                'log_timestamp' => $dbw->timestamp( $this->timestamp ),
                                'log_namespace' => $this->getTitle()->getNamespace(),
                                'log_title' => $this->getTitle()->getDBkey(),
-                               'log_comment' => $this->getComment(),
                                # 'log_user_text' => $this->user_text,
                                'log_params' => $this->params ],
                        __METHOD__
@@ -730,9 +730,8 @@ class WikiRevision {
                        'log_user_text' => $userText,
                        'log_namespace' => $this->getTitle()->getNamespace(),
                        'log_title' => $this->getTitle()->getDBkey(),
-                       'log_comment' => $this->getComment(),
                        'log_params' => $this->params
-               ];
+               ] + CommentStore::newKey( 'log_comment' )->insert( $dbw, $this->getComment() );
                $dbw->insert( 'logging', $data, __METHOD__ );
 
                return true;
index b832d45..645fa8a 100644 (file)
@@ -1190,4 +1190,25 @@ abstract class DatabaseUpdater {
                        $wgContentHandlerUseDB = $this->holdContentHandlerUseDB;
                }
        }
+
+       /**
+        * Migrate comments to the new 'comment' table
+        * @since 1.30
+        */
+       protected function migrateComments() {
+               global $wgCommentTableSchemaMigrationStage;
+               if ( $wgCommentTableSchemaMigrationStage >= MIGRATION_WRITE_NEW &&
+                       !$this->updateRowExists( 'MigrateComments' )
+               ) {
+                       $this->output(
+                               "Migrating comments to the 'comments' table, printing progress markers. For large\n" .
+                               "databases, you may want to hit Ctrl-C and do this manually with\n" .
+                               "maintenance/migrateComments.php.\n"
+                       );
+                       $task = $this->maintenance->runChild( 'MigrateComments', 'migrateComments.php' );
+                       $task->execute();
+                       $this->output( "done.\n" );
+               }
+       }
+
 }
index 58728a3..b42ae46 100644 (file)
@@ -170,7 +170,6 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'doLogUsertextPopulation' ],
                        [ 'doLogSearchPopulation' ],
                        [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
-                       [ 'addIndex', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ],
                        [ 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ],
                        [ 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ],
                        [ 'doUpdateTranscacheField' ],
@@ -305,6 +304,29 @@ class MysqlUpdater extends DatabaseUpdater {
                        // 1.30
                        [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
                        [ 'addTable', 'ip_changes', 'patch-ip_changes.sql' ],
+                       [ 'renameIndex', 'categorylinks', 'cl_from', 'PRIMARY', false,
+                               'patch-categorylinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'templatelinks', 'tl_from', 'PRIMARY', false,
+                               'patch-templatelinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'pagelinks', 'pl_from', 'PRIMARY', false, 'patch-pagelinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'text', 'old_id', 'PRIMARY', false, 'patch-text-fix-pk.sql' ],
+                       [ 'renameIndex', 'imagelinks', 'il_from', 'PRIMARY', false, 'patch-imagelinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'iwlinks', 'iwl_from', 'PRIMARY', false, 'patch-iwlinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'langlinks', 'll_from', 'PRIMARY', false, 'patch-langlinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'log_search', 'ls_field_val', 'PRIMARY', false, 'patch-log_search-fix-pk.sql' ],
+                       [ 'renameIndex', 'module_deps', 'md_module_skin', 'PRIMARY', false,
+                               'patch-module_deps-fix-pk.sql' ],
+                       [ 'renameIndex', 'objectcache', 'keyname', 'PRIMARY', false, 'patch-objectcache-fix-pk.sql' ],
+                       [ 'renameIndex', 'querycache_info', 'qci_type', 'PRIMARY', false,
+                               'patch-querycache_info-fix-pk.sql' ],
+                       [ 'renameIndex', 'site_stats', 'ss_row_id', 'PRIMARY', false, 'patch-site_stats-fix-pk.sql' ],
+                       [ 'renameIndex', 'transcache', 'tc_url_idx', 'PRIMARY', false, 'patch-transcache-fix-pk.sql' ],
+                       [ 'renameIndex', 'user_former_groups', 'ufg_user_group', 'PRIMARY', false,
+                               'patch-user_former_groups-fix-pk.sql' ],
+                       [ 'renameIndex', 'user_properties', 'user_properties_user_property', 'PRIMARY', false,
+                               'patch-user_properties-fix-pk.sql' ],
+                       [ 'addTable', 'comment', 'patch-comment-table.sql' ],
+                       [ 'migrateComments' ],
                ];
        }
 
index e262eda..00b9661 100644 (file)
@@ -123,6 +123,9 @@ class OracleUpdater extends DatabaseUpdater {
                        [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ],
                        [ 'addField', 'user_groups', 'ug_expiry', 'patch-user_groups-ug_expiry.sql' ],
 
+                       // 1.30
+                       [ 'doAutoIncrementTriggers' ],
+
                        // KEEP THIS AT THE BOTTOM!!
                        [ 'doRebuildDuplicateFunction' ],
 
@@ -273,6 +276,30 @@ class OracleUpdater extends DatabaseUpdater {
                $this->output( "ok\n" );
        }
 
+       /**
+        * Add auto-increment triggers
+        */
+       protected function doAutoIncrementTriggers() {
+               $this->output( "Adding auto-increment triggers ... " );
+
+               $meta = $this->db->query( 'SELECT trigger_name FROM user_triggers WHERE table_owner = \'' .
+                       strtoupper( $this->db->getDBname() ) .
+                       '\' AND trigger_name = \'' .
+                       $this->db->tablePrefix() .
+                       'PAGE_DEFAULT_PAGE_ID\''
+               );
+               $row = $meta->fetchRow();
+               if ( $row['column_name'] ) {
+                       $this->output( "seems to be up to date.\n" );
+
+                       return;
+               }
+
+               $this->applyPatch( 'patch-auto_increment_triggers.sql', false );
+
+               $this->output( "ok\n" );
+       }
+
        /**
         * rebuilding of the function that duplicates tables for tests
         */
index d8db6a2..e5a5c94 100644 (file)
@@ -455,6 +455,32 @@ class PostgresUpdater extends DatabaseUpdater {
 
                        // 1.30
                        [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
+                       [ 'setDefault', 'revision', 'rev_comment', '' ],
+                       [ 'changeNullableField', 'revision', 'rev_comment', 'NOT NULL', true ],
+                       [ 'setDefault', 'archive', 'ar_comment', '' ],
+                       [ 'changeNullableField', 'archive', 'ar_comment', 'NOT NULL', true ],
+                       [ 'addPgField', 'archive', 'ar_comment_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'ipblocks', 'ipb_reason', '' ],
+                       [ 'addPgField', 'ipblocks', 'ipb_reason_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'image', 'img_description', '' ],
+                       [ 'setDefault', 'oldimage', 'oi_description', '' ],
+                       [ 'changeNullableField', 'oldimage', 'oi_description', 'NOT NULL', true ],
+                       [ 'addPgField', 'oldimage', 'oi_description_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'filearchive', 'fa_deleted_reason', '' ],
+                       [ 'changeNullableField', 'filearchive', 'fa_deleted_reason', 'NOT NULL', true ],
+                       [ 'addPgField', 'filearchive', 'fa_deleted_reason_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'filearchive', 'fa_description', '' ],
+                       [ 'addPgField', 'filearchive', 'fa_description_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'recentchanges', 'rc_comment', '' ],
+                       [ 'changeNullableField', 'recentchanges', 'rc_comment', 'NOT NULL', true ],
+                       [ 'addPgField', 'recentchanges', 'rc_comment_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'logging', 'log_comment', '' ],
+                       [ 'changeNullableField', 'logging', 'log_comment', 'NOT NULL', true ],
+                       [ 'addPgField', 'logging', 'log_comment_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'protected_titles', 'pt_reason', '' ],
+                       [ 'changeNullableField', 'protected_titles', 'pt_reason', 'NOT NULL', true ],
+                       [ 'addPgField', 'protected_titles', 'pt_reason_id', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'addTable', 'comment', 'patch-comment-table.sql' ],
                ];
        }
 
@@ -761,7 +787,7 @@ END;
                }
        }
 
-       protected function changeNullableField( $table, $field, $null ) {
+       protected function changeNullableField( $table, $field, $null, $update = false ) {
                $fi = $this->db->fieldInfo( $table, $field );
                if ( is_null( $fi ) ) {
                        $this->output( "...ERROR: expected column $table.$field to exist\n" );
@@ -771,6 +797,9 @@ END;
                        # # It's NULL - does it need to be NOT NULL?
                        if ( 'NOT NULL' === $null ) {
                                $this->output( "Changing '$table.$field' to not allow NULLs\n" );
+                               if ( $update ) {
+                                       $this->db->query( "UPDATE $table SET $field = DEFAULT WHERE $field IS NULL" );
+                               }
                                $this->db->query( "ALTER TABLE $table ALTER $field SET NOT NULL" );
                        } else {
                                $this->output( "...column '$table.$field' is already set as NULL\n" );
index 1e43d3e..d0ed822 100644 (file)
@@ -54,7 +54,6 @@ class SqliteUpdater extends DatabaseUpdater {
                        [ 'doLogUsertextPopulation' ],
                        [ 'doLogSearchPopulation' ],
                        [ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
-                       [ 'addIndex', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ],
                        [ 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ],
                        [ 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ],
                        [ 'doUpdateTranscacheField' ],
@@ -169,6 +168,29 @@ class SqliteUpdater extends DatabaseUpdater {
                        // 1.30
                        [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
                        [ 'addTable', 'ip_changes', 'patch-ip_changes.sql' ],
+                       [ 'renameIndex', 'categorylinks', 'cl_from', 'PRIMARY', false,
+                               'patch-categorylinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'templatelinks', 'tl_from', 'PRIMARY', false,
+                               'patch-templatelinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'pagelinks', 'pl_from', 'PRIMARY', false, 'patch-pagelinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'text', 'old_id', 'PRIMARY', false, 'patch-text-fix-pk.sql' ],
+                       [ 'renameIndex', 'imagelinks', 'il_from', 'PRIMARY', false, 'patch-imagelinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'iwlinks', 'iwl_from', 'PRIMARY', false, 'patch-iwlinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'langlinks', 'll_from', 'PRIMARY', false, 'patch-langlinks-fix-pk.sql' ],
+                       [ 'renameIndex', 'log_search', 'ls_field_val', 'PRIMARY', false, 'patch-log_search-fix-pk.sql' ],
+                       [ 'renameIndex', 'module_deps', 'md_module_skin', 'PRIMARY', false,
+                               'patch-module_deps-fix-pk.sql' ],
+                       [ 'renameIndex', 'objectcache', 'keyname', 'PRIMARY', false, 'patch-objectcache-fix-pk.sql' ],
+                       [ 'renameIndex', 'querycache_info', 'qci_type', 'PRIMARY', false,
+                               'patch-querycache_info-fix-pk.sql' ],
+                       [ 'renameIndex', 'site_stats', 'ss_row_id', 'PRIMARY', false, 'patch-site_stats-fix-pk.sql' ],
+                       [ 'renameIndex', 'transcache', 'tc_url_idx', 'PRIMARY', false, 'patch-transcache-fix-pk.sql' ],
+                       [ 'renameIndex', 'user_former_groups', 'ufg_user_group', 'PRIMARY', false,
+                               'patch-user_former_groups-fix-pk.sql' ],
+                       [ 'renameIndex', 'user_properties', 'user_properties_user_property', 'PRIMARY', false,
+                               'patch-user_properties-fix-pk.sql' ],
+                       [ 'addTable', 'comment', 'patch-comment-table.sql' ],
+                       [ 'migrateComments' ],
                ];
        }
 
index dacdf81..2afd2ca 100644 (file)
@@ -6,7 +6,8 @@
                        "Tjernobyl",
                        "Thomsen",
                        "MGA73",
-                       "Mads Haupt"
+                       "Mads Haupt",
+                       "Joedalton"
                ]
        },
        "config-desc": "Installationsprogrammet til MediaWiki",
        "config-localsettings-upgrade": "En <code>LocalSettings.php</code>-fil er blevet fundet.\nFor at opgradere imstallationen, skriv venligst værdien af <code>$wgUpgradeKey</code> i boksen nedenfor.\nDu finder denne i <code>LocalSettings.php</code>.",
        "config-localsettings-cli-upgrade": "En <code>LocalSettings.php</code>-fil er blevet fundet.\nFor at opgradere installationen skal du køre <code>update.php</code> i stedet for",
        "config-localsettings-key": "Opgraderingsnøgle:",
-       "config-localsettings-badkey": "Den nøgle du indtastede er forkert.",
+       "config-localsettings-badkey": "Den indtastede opgraderingsnøgle er forkert.",
        "config-upgrade-key-missing": "En eksisterende installation af MediaWiki er blevet fundet.\nFor at opgradere denne installation skal du tilføje følgende linje i bunden af din <code>LocalSettings.php</code>:\n\n$1",
+       "config-localsettings-incomplete": "Den eksisterende <code>LocalSettings.php</code> ser ud til at være ufuldstændig.\nVariablen $1 er ikke angivet.\nÆndr venligst <code>LocalSettings.php</code> så denne variabel er angivet, og klik på »{{int:Config-continue}}«.",
+       "config-localsettings-connection-error": "Der opstod en fejl under tilslutningen til databasen med indstillingerne angivet i <code>LocalSettings.php</code>. Ret venligst disse indstillinger og prøv igen.\n\n$1",
+       "config-session-error": "Der opstod en fejl under start af session: $1",
        "config-your-language": "Dit sprog:",
        "config-your-language-help": "Vælg et sprog som du vil bruge under installationen.",
        "config-wiki-language": "Wiki-sprog:",
        "config-db-type": "Databasetype:",
        "config-db-host": "Databasevært:",
        "config-db-name": "Databasenavn:",
+       "config-db-install-account": "Brugerkonto for installation",
+       "config-db-username": "Databasens brugernavn:",
+       "config-db-password": "Databasens adgangskode:",
+       "config-db-install-username": "Indtast brugernavnet som vil blive brugt til at forbinde til databasen under installationsprocessen.\nDette er ikke brugernavnet for MediaWiki-kontoen; det er brugernavnet på din database.",
        "config-mysql-old": "MySQL $1 eller nyere kræves. Du har $2.",
+       "config-type-mssql": "Microsoft SQL-server",
        "config-header-mysql": "MySQL-indstillinger",
        "config-header-postgres": "PostgreSQL-indstillinger",
        "config-header-sqlite": "SQLite-indstillinger",
        "config-header-oracle": "Oracle-indstillinger",
        "config-invalid-db-type": "Ugyldig databasetype",
+       "config-mssql-windowsauth": "Windows-godkendelse",
+       "config-site-name": "Navn på wiki:",
+       "config-site-name-blank": "Indtast et hjemmesidenavn.",
+       "config-ns-generic": "Projekt",
+       "config-admin-box": "Administratorkonto",
+       "config-admin-name": "Dit brugernavn:",
+       "config-admin-password": "Adgangskode:",
+       "config-admin-password-confirm": "Tast adgangskoden igen:",
+       "config-admin-email": "E-postadresse:",
+       "config-optional-continue": "Stil mig flere spørgsmål.",
+       "config-profile-wiki": "Åbn wiki",
+       "config-profile-no-anon": "Kontooprettelse er krævet",
+       "config-profile-fishbowl": "Kun godkendte redaktører",
+       "config-profile-private": "Privat wiki",
+       "config-license": "Ophavsret og licens:",
+       "config-license-pd": "Offentlig ejendom",
        "config-email-usertalk": "Aktiver notifikationer for brugerdiskussionsside",
+       "config-upload-deleted": "Mappe for slettede filer:",
+       "config-help": "hjælp",
+       "config-help-tooltip": "klik for at udvide",
        "mainpagetext": "'''MediaWiki er nu installeret.'''",
        "mainpagedocfooter": "Se [https://meta.wikimedia.org/wiki/Help:Contents brugervejledningen] for oplysninger om brugen af wikiprogrammellet.\n\n== At komme i gang ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Listen over opsætningsmuligheder]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki ofte stillede spørgsmål]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Postliste angående udgivelser af MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Oversæt MediaWiki til dit sprog]"
 }
index 021aa5d..be2e65a 100644 (file)
        "config-connection-error": "$1.\n\nVerifica el servidor, el nombre de usuario y la contraseña, e intenta de nuevo.",
        "config-invalid-schema": "El esquema de la base de datos \"$1\"  es inválido.\nUse sólo carateres ASCII: letras (a-z, A-Z), guarismos (0-9) y guiones bajos (_).",
        "config-db-sys-create-oracle": "El instalador sólo admite el empleo de cuentas SYSDBA como método para crear una cuenta nueva.",
-       "config-db-sys-user-exists-oracle": "La cuenta de usuario \"$1\" ya existe. ¡SYSDBA sólo puede utilizarse para crear una nueva cuenta!",
+       "config-db-sys-user-exists-oracle": "La cuenta de usuario «$1» ya existe. SYSDBA solo puede utilizarse para crear cuentas nuevas.",
        "config-postgres-old": "Se requiere PostgreSQL $1 o posterior. Tienes la versión $2.",
        "config-mssql-old": "Se requiere Microsoft SQL Server $1 o posterior. Tienes la versión $2.",
        "config-sqlite-name-help": "Elige el nombre que identificará a tu wiki.\nNo uses espacios o guiones.\nEste nombre se usará como nombre del archivo de datos de SQLite.",
        "config-pingback-help": "Si seleccionas esta opción, MediaWiki enviará periódicamente a https://www.mediawiki.org datos básicos sobre esta instancia de MediaWiki. Se trata de datos tales como el tipo de sistema, la versión de PHP y la base de datos elegida. La Fundación Wikimedia comparte estos datos con los desarrolladores de MediaWiki para ayudar a guiar el desarrollo futuro. Se enviarán los siguientes datos para tu sistema:\n<pre>$1</pre>",
        "config-almost-done": "¡Ya casi has terminado!\nAhora puedes saltarte el resto de los pasos e instalar el wiki ya.",
        "config-optional-continue": "Hazme más preguntas.",
-       "config-optional-skip": "Ya estoy aburrido, sólo instala el wiki.",
+       "config-optional-skip": "Ya me aburrí. Tan solo instala el wiki.",
        "config-profile": "Perfil de derechos de usuario:",
        "config-profile-wiki": "Wiki abierto",
        "config-profile-no-anon": "Creación de cuenta requerida",
-       "config-profile-fishbowl": "Sólo editores autorizados",
+       "config-profile-fishbowl": "Solo editores autorizados",
        "config-profile-private": "Wiki privado",
        "config-profile-help": "Los wikis funcionan mejor cuando dejas que los edite tanta gente como sea posible.\nEn MediaWiki, es fácil revisar los cambios recientes y revertir los daños realizados por usuarios malintencionados o novatos.\nSin embargo, muchos han encontrado que MediaWiki es útil para una amplia variedad de funciones, y a veces no es fácil convencer a todos de los beneficios de la forma wiki.\nPor lo tanto tienes la elección.\n\nEl modelo <strong>{{int:config-profile-wiki}}</strong> permite que cualquiera pueda editar, sin siquiera iniciar sesión.\nUn wiki con <strong>{{int:config-profile-no-anon}}</strong> ofrece rendición de cuentas adicional, pero puede disuadir a colaboradores casuales.\n\nEl modelo <strong>{{int:config-profile-fishbowl}}</strong> permite editar a los usuarios autorizados, pero el público puede ver las páginas, incluyendo el historial.\nUn <strong>{{int:config-profile-private}}</strong> sólo permite ver páginas a los usuarios autorizados, el mismo grupo al que le está permitido editar.\n\nConfiguraciones más complejas de permisos de usuario están disponibles después de la instalación. Consulta [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights la entrada correspondiente del manual].",
        "config-license": "Derechos de autor y licencia:",
index 430345e..0591cbb 100644 (file)
@@ -4,7 +4,8 @@
                        "An13sa",
                        "පසිඳු කාවින්ද",
                        "Subi",
-                       "Sator"
+                       "Sator",
+                       "Mikel Ibaiba"
                ]
        },
        "config-desc": "MediaWiki instalatzailea",
        "config-license-pd": "Domeinu Askea",
        "config-license-cc-choose": "Aukeratu Creative Commons lizentzia pertsonalizatua",
        "config-email-settings": "E-posta hobespenak",
+       "config-email-sender": "Itzuli helbide elektronikoa:",
        "config-upload-settings": "Irudi eta fitxategi igoerak",
        "config-upload-enable": "Fitxategi igoera gaitu",
+       "config-upload-deleted": "Ezabatutako artxiboentzako direktorioa:",
        "config-logo": "Logo URL:",
        "config-instantcommons": "Instant Commons gaitu",
        "config-cc-again": "Berriz aukeratu...",
        "config-extensions": "Luzapenak",
        "config-skins": "Itxurak",
        "config-install-step-done": "egina",
+       "config-install-step-failed": "Huts egin du",
        "config-install-extensions": "Luzapenak barne",
        "config-install-database": "Datu-basea konfiguratu",
        "config-install-schema": "Eskema sortu",
+       "config-install-user": "Datubase erabiltzailea sortzen",
        "config-install-user-alreadyexists": "\"$1\" erabiltzailea badago.",
+       "config-install-user-create-failed": "$1 erabiltzailea sortzerakoan huts egin du: $2",
+       "config-install-user-grant-failed": "$1ri baimena emateak huts egin du: $2",
        "config-install-tables": "Taulak sortzen",
        "config-install-interwiki-list": "Ezin izan da <code>interwiki.list</code> fitxategia irakurri.",
        "config-install-stats": "Estatistikak hasten",
index bde8c69..3bfb531 100644 (file)
 
 use IPSet\IPSet;
 
-// Some regex definition to "play" with IP address and IP address blocks
+// Some regex definition to "play" with IP address and IP address ranges
 
 // An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
 define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
 define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
-// An IPv4 block is an IP address and a prefix (d1 to d32)
+// An IPv4 range is an IP address and a prefix (d1 to d32)
 define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
-define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
+define( 'RE_IP_RANGE', RE_IP_ADD . '\/' . RE_IP_PREFIX );
 
 // An IPv6 address is made up of 8 words (each x0000 to xFFFF).
 // However, the "::" abbreviation can be used on consecutive x0000 words.
@@ -47,8 +47,8 @@ define( 'RE_IPV6_ADD',
                RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
        ')'
 );
-// An IPv6 block is an IP address and a prefix (d1 to d128)
-define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// An IPv6 range is an IP address and a prefix (d1 to d128)
+define( 'RE_IPV6_RANGE', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
 // For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
 define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
 define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
@@ -64,7 +64,7 @@ define( 'IP_ADDRESS_STRING',
 
 /**
  * A collection of public static functions to play with IP address
- * and IP blocks.
+ * and IP ranges.
  */
 class IP {
 
@@ -116,16 +116,30 @@ class IP {
        }
 
        /**
-        * Validate an IP Block (valid address WITH a valid prefix).
+        * Validate an IP range (valid address with a valid CIDR prefix).
         * SIIT IPv4-translated addresses are rejected.
         * @note canonicalize() tries to convert translated addresses to IPv4.
         *
-        * @param string $ipblock
+        * @deprecated since 1.30. Use the equivalent IP::isValidRange().
+        * @param string $ipRange
         * @return bool True if it is valid
         */
-       public static function isValidBlock( $ipblock ) {
-               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
-                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
+       public static function isValidBlock( $ipRange ) {
+               return self::isValidRange( $ipRange );
+       }
+
+       /**
+        * Validate an IP range (valid address with a valid CIDR prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ipRange
+        * @return bool True if it is valid
+        * @since 1.30
+        */
+       public static function isValidRange( $ipRange ) {
+               return ( preg_match( '/^' . RE_IPV6_RANGE . '$/', $ipRange )
+                       || preg_match( '/^' . RE_IP_RANGE . '$/', $ipRange ) );
        }
 
        /**
index bdac06c..ac59bd6 100644 (file)
@@ -39,8 +39,6 @@ class DatabasePostgres extends Database {
        /** @var int The number of rows affected as an integer */
        protected $mAffectedRows = null;
 
-       /** @var int */
-       private $mInsertId = null;
        /** @var float|string */
        private $numericVersion = null;
        /** @var string Connect string to open a PostgreSQL connection */
@@ -352,14 +350,10 @@ class DatabasePostgres extends Database {
                return pg_field_name( $res, $n );
        }
 
-       /**
-        * Return the result of the last call to nextSequenceValue();
-        * This must be called after nextSequenceValue().
-        *
-        * @return int|null
-        */
        public function insertId() {
-               return $this->mInsertId;
+               $res = $this->query( "SELECT lastval()" );
+               $row = $this->fetchRow( $res );
+               return is_null( $row[0] ) ? null : (int)$row[0];
        }
 
        public function dataSeek( $res, $row ) {
@@ -521,6 +515,10 @@ __INDEXATTR__;
        public function selectSQLText(
                $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
        ) {
+               if ( is_string( $options ) ) {
+                       $options = [ $options ];
+               }
+
                // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
                // to the parent function to get the actual SQL text.
                // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
@@ -532,12 +530,28 @@ __INDEXATTR__;
                        $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
                        if ( $forUpdateKey !== false && $join_conds ) {
                                unset( $options[$forUpdateKey] );
+                               $options['FOR UPDATE'] = [];
+
+                               // All tables not in $join_conds are good
+                               foreach ( $table as $alias => $name ) {
+                                       if ( is_numeric( $alias ) ) {
+                                               $alias = $name;
+                                       }
+                                       if ( !isset( $join_conds[$alias] ) ) {
+                                               $options['FOR UPDATE'][] = $alias;
+                                       }
+                               }
 
                                foreach ( $join_conds as $table_cond => $join_cond ) {
                                        if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
                                                $options['FOR UPDATE'][] = $table_cond;
                                        }
                                }
+
+                               // Quote alias names so $this->tableName() won't mangle them
+                               $options['FOR UPDATE'] = array_map( function ( $name ) use ( $table ) {
+                                       return isset( $table[$name] ) ? $this->addIdentifierQuotes( $name ) : $name;
+                               }, $options['FOR UPDATE'] );
                        }
 
                        if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
@@ -756,12 +770,7 @@ __INDEXATTR__;
        }
 
        public function nextSequenceValue( $seqName ) {
-               $safeseq = str_replace( "'", "''", $seqName );
-               $res = $this->query( "SELECT nextval('$safeseq')" );
-               $row = $this->fetchRow( $res );
-               $this->mInsertId = is_null( $row[0] ) ? null : (int)$row[0];
-
-               return $this->mInsertId;
+               return new NextSequenceValue;
        }
 
        /**
@@ -1204,6 +1213,8 @@ SQL;
                                $s = pg_escape_bytea( $conn, $s->fetch() );
                        }
                        return "'$s'";
+               } elseif ( $s instanceof NextSequenceValue ) {
+                       return 'DEFAULT';
                }
 
                return "'" . pg_escape_string( $conn, $s ) . "'";
index 9375efc..736447f 100644 (file)
@@ -1114,15 +1114,20 @@ interface IDatabase {
        public function anyString();
 
        /**
-        * Returns an appropriately quoted sequence value for inserting a new row.
-        * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
-        * subclass will return an integer, and save the value for insertId()
+        * Deprecated method, calls should be removed.
         *
-        * Any implementation of this function should *not* involve reusing
-        * sequence numbers created for rolled-back transactions.
-        * See https://bugs.mysql.com/bug.php?id=30767 for details.
+        * This was formerly used for PostgreSQL and Oracle to handle
+        * self::insertId() auto-incrementing fields. It is no longer necessary
+        * since DatabasePostgres::insertId() has been reimplemented using
+        * `lastval()` and Oracle has been reimplemented using triggers.
+        *
+        * Implementations should return null if inserting `NULL` into an
+        * auto-incrementing field works, otherwise it should return an instance of
+        * NextSequenceValue and filter it on calls to relevant methods.
+        *
+        * @deprecated since 1.30, no longer needed
         * @param string $seqName
-        * @return null|int
+        * @return null|NextSequenceValue
         */
        public function nextSequenceValue( $seqName );
 
diff --git a/includes/libs/rdbms/database/utils/NextSequenceValue.php b/includes/libs/rdbms/database/utils/NextSequenceValue.php
new file mode 100644 (file)
index 0000000..44bf0dd
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+namespace Wikimedia\Rdbms;
+
+/**
+ * Used by Database::nextSequenceValue() so Database::insert() can detect
+ * values coming from the deprecated function.
+ * @since 1.30
+ * @deprecated since 1.30, only exists for backwards compatibility
+ */
+class NextSequenceValue {
+}
index 0091524..91d98dc 100644 (file)
@@ -28,7 +28,7 @@ class DBConnectionError extends DBExpectedError {
         * @param IDatabase $db Object throwing the error
         * @param string $error Error text
         */
-       function __construct( IDatabase $db = null, $error = 'unknown error' ) {
+       public function __construct( IDatabase $db = null, $error = 'unknown error' ) {
                $msg = 'Cannot access the database';
                if ( trim( $error ) != '' ) {
                        $msg .= ": $error";
index d65e2d3..2f7499b 100644 (file)
@@ -36,7 +36,7 @@ class DBError extends Exception {
         * @param IDatabase $db Object which threw the error
         * @param string $error A simple error message to be used for debugging
         */
-       function __construct( IDatabase $db = null, $error ) {
+       public function __construct( IDatabase $db = null, $error ) {
                $this->db = $db;
                parent::__construct( $error );
        }
index 4f65efa..31d8c27 100644 (file)
@@ -36,7 +36,7 @@ class DBExpectedError extends DBError implements MessageSpecifier, ILocalizedExc
        /** @var string[] Message parameters */
        protected $params;
 
-       function __construct( IDatabase $db = null, $error, array $params = [] ) {
+       public function __construct( IDatabase $db = null, $error, array $params = [] ) {
                parent::__construct( $db, $error );
                $this->params = $params;
        }
index 6a4076f..a8ea3ad 100644 (file)
@@ -41,7 +41,7 @@ class DBQueryError extends DBExpectedError {
         * @param string $sql
         * @param string $fname
         */
-       function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
+       public function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
                if ( $db instanceof Database && $db->wasConnectionError( $errno ) ) {
                        $message = "A connection error occured. \n" .
                                "Query: $sql\n" .
index e45b9f3..d2622e1 100644 (file)
@@ -25,7 +25,7 @@ namespace Wikimedia\Rdbms;
  * @ingroup Database
  */
 class DBTransactionSizeError extends DBTransactionError {
-       function getKey() {
+       public function getKey() {
                return 'transaction-duration-limit-exceeded';
        }
 }
index fa94fe5..6587304 100644 (file)
@@ -170,19 +170,21 @@ class DatabaseLogEntry extends LogEntryBase {
         * @return array
         */
        public static function getSelectQueryData() {
-               $tables = [ 'logging', 'user' ];
+               $commentQuery = CommentStore::newKey( 'log_comment' )->getJoin();
+
+               $tables = [ 'logging', 'user' ] + $commentQuery['tables'];
                $fields = [
                        'log_id', 'log_type', 'log_action', 'log_timestamp',
                        'log_user', 'log_user_text',
                        'log_namespace', 'log_title', // unused log_page
-                       'log_comment', 'log_params', 'log_deleted',
+                       'log_params', 'log_deleted',
                        'user_id', 'user_name', 'user_editcount',
-               ];
+               ] + $commentQuery['fields'];
 
                $joins = [
                        // IPs don't have an entry in user table
                        'user' => [ 'LEFT JOIN', 'log_user=user_id' ],
-               ];
+               ] + $commentQuery['joins'];
 
                return [
                        'tables' => $tables,
@@ -322,7 +324,7 @@ class DatabaseLogEntry extends LogEntryBase {
        }
 
        public function getComment() {
-               return $this->row->log_comment;
+               return CommentStore::newKey( 'log_comment' )->getComment( $this->row )->text;
        }
 
        public function getDeleted() {
@@ -380,7 +382,9 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
        }
 
        public function getComment() {
-               return $this->row->rc_comment;
+               return CommentStore::newKey( 'rc_comment' )
+                       // Legacy because the row probably used RecentChange::selectFields()
+                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $this->row )->text;
        }
 
        public function getDeleted() {
@@ -624,12 +628,12 @@ class ManualLogEntry extends LogEntryBase {
                        'log_namespace' => $this->getTarget()->getNamespace(),
                        'log_title' => $this->getTarget()->getDBkey(),
                        'log_page' => $this->getTarget()->getArticleID(),
-                       'log_comment' => $comment,
                        'log_params' => LogEntryBase::makeParamBlob( $params ),
                ];
                if ( isset( $this->deleted ) ) {
                        $data['log_deleted'] = $this->deleted;
                }
+               $data += CommentStore::newKey( 'log_comment' )->insert( $dbw, $comment );
 
                $dbw->insert( 'logging', $data, __METHOD__ );
                $this->id = $dbw->insertId();
index a085e3e..257f76d 100644 (file)
@@ -104,9 +104,9 @@ class LogPage {
                        'log_namespace' => $this->target->getNamespace(),
                        'log_title' => $this->target->getDBkey(),
                        'log_page' => $this->target->getArticleID(),
-                       'log_comment' => $this->comment,
                        'log_params' => $this->params
                ];
+               $data += CommentStore::newKey( 'log_comment' )->insert( $dbw, $this->comment );
                $dbw->insert( 'logging', $data, __METHOD__ );
                $newId = $dbw->insertId();
 
index 11e1a30..f6580e9 100644 (file)
@@ -171,20 +171,21 @@ class PageArchive {
 
        /**
         * List the revisions of the given page. Returns result wrapper with
-        * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields.
+        * various archive table fields.
         *
         * @return ResultWrapper
         */
        public function listRevisions() {
                $dbr = wfGetDB( DB_REPLICA );
+               $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
 
-               $tables = [ 'archive' ];
+               $tables = [ 'archive' ] + $commentQuery['tables'];
 
                $fields = [
                        'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
-                       'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
+                       'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
                        'ar_page_id'
-               ];
+               ] + $commentQuery['fields'];
 
                if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
                        $fields[] = 'ar_content_format';
@@ -196,7 +197,7 @@ class PageArchive {
 
                $options = [ 'ORDER BY' => 'ar_timestamp DESC' ];
 
-               $join_conds = [];
+               $join_conds = [] + $commentQuery['joins'];
 
                ChangeTags::modifyDisplayQuery(
                        $tables,
@@ -248,11 +249,13 @@ class PageArchive {
         */
        public function getRevision( $timestamp ) {
                $dbr = wfGetDB( DB_REPLICA );
+               $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
+
+               $tables = [ 'archive' ] + $commentQuery['tables'];
 
                $fields = [
                        'ar_rev_id',
                        'ar_text',
-                       'ar_comment',
                        'ar_user',
                        'ar_user_text',
                        'ar_timestamp',
@@ -262,19 +265,27 @@ class PageArchive {
                        'ar_deleted',
                        'ar_len',
                        'ar_sha1',
-               ];
+               ] + $commentQuery['fields'];
 
                if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
                        $fields[] = 'ar_content_format';
                        $fields[] = 'ar_content_model';
                }
 
-               $row = $dbr->selectRow( 'archive',
+               $join_conds = [] + $commentQuery['joins'];
+
+               $row = $dbr->selectRow(
+                       $tables,
                        $fields,
-                       [ 'ar_namespace' => $this->title->getNamespace(),
+                       [
+                               'ar_namespace' => $this->title->getNamespace(),
                                'ar_title' => $this->title->getDBkey(),
-                               'ar_timestamp' => $dbr->timestamp( $timestamp ) ],
-                       __METHOD__ );
+                               'ar_timestamp' => $dbr->timestamp( $timestamp )
+                       ],
+                       __METHOD__,
+                       [],
+                       $join_conds
+               );
 
                if ( $row ) {
                        return Revision::newFromArchiveRow( $row, [ 'title' => $this->title ] );
@@ -552,12 +563,15 @@ class PageArchive {
                        $oldWhere['ar_timestamp'] = array_map( [ &$dbw, 'timestamp' ], $timestamps );
                }
 
+               $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
+
+               $tables = [ 'archive', 'revision' ] + $commentQuery['tables'];
+
                $fields = [
                        'ar_id',
                        'ar_rev_id',
                        'rev_id',
                        'ar_text',
-                       'ar_comment',
                        'ar_user',
                        'ar_user_text',
                        'ar_timestamp',
@@ -568,24 +582,28 @@ class PageArchive {
                        'ar_page_id',
                        'ar_len',
                        'ar_sha1'
-               ];
+               ] + $commentQuery['fields'];
 
                if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
                        $fields[] = 'ar_content_format';
                        $fields[] = 'ar_content_model';
                }
 
+               $join_conds = [
+                       'revision' => [ 'LEFT JOIN', 'ar_rev_id=rev_id' ],
+               ] + $commentQuery['joins'];
+
                /**
                 * Select each archived revision...
                 */
                $result = $dbw->select(
-                       [ 'archive', 'revision' ],
+                       $tables,
                        $fields,
                        $oldWhere,
                        __METHOD__,
                        /* options */
                        [ 'ORDER BY' => 'ar_timestamp' ],
-                       [ 'revision' => [ 'LEFT JOIN', 'ar_rev_id=rev_id' ] ]
+                       $join_conds
                );
 
                $rev_count = $result->numRows();
index edccc66..790845e 100644 (file)
@@ -2484,6 +2484,7 @@ class WikiPage implements Page, IDBAccessObject {
                        $cascade = false;
 
                        if ( $limit['create'] != '' ) {
+                               $commentFields = CommentStore::newKey( 'pt_reason' )->insert( $dbw, $reason );
                                $dbw->replace( 'protected_titles',
                                        [ [ 'pt_namespace', 'pt_title' ] ],
                                        [
@@ -2493,8 +2494,7 @@ class WikiPage implements Page, IDBAccessObject {
                                                'pt_timestamp' => $dbw->timestamp(),
                                                'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
                                                'pt_user' => $user->getId(),
-                                               'pt_reason' => $reason,
-                                       ], __METHOD__
+                                       ] + $commentFields, __METHOD__
                                );
                                $logParamsDetails[] = [
                                        'type' => 'create',
@@ -2746,7 +2746,7 @@ class WikiPage implements Page, IDBAccessObject {
                $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
                $tags = [], $logsubtype = 'delete'
        ) {
-               global $wgUser, $wgContentHandlerUseDB;
+               global $wgUser, $wgContentHandlerUseDB, $wgCommentTableSchemaMigrationStage;
 
                wfDebug( __METHOD__ . "\n" );
 
@@ -2810,6 +2810,9 @@ class WikiPage implements Page, IDBAccessObject {
                        $content = null;
                }
 
+               $revCommentStore = new CommentStore( 'rev_comment' );
+               $arCommentStore = new CommentStore( 'ar_comment' );
+
                $fields = Revision::selectFields();
                $bitfield = false;
 
@@ -2827,20 +2830,23 @@ class WikiPage implements Page, IDBAccessObject {
                // the rev_deleted field, which is reserved for this purpose.
 
                // Get all of the page revisions
+               $commentQuery = $revCommentStore->getJoin();
                $res = $dbw->select(
-                       'revision',
-                       $fields,
+                       [ 'revision' ] + $commentQuery['tables'],
+                       $fields + $commentQuery['fields'],
                        [ 'rev_page' => $id ],
                        __METHOD__,
-                       'FOR UPDATE'
+                       'FOR UPDATE',
+                       $commentQuery['joins']
                );
                // Build their equivalent archive rows
                $rowsInsert = [];
+               $revids = [];
                foreach ( $res as $row ) {
+                       $comment = $revCommentStore->getComment( $row );
                        $rowInsert = [
                                'ar_namespace'  => $namespace,
                                'ar_title'      => $dbKey,
-                               'ar_comment'    => $row->rev_comment,
                                'ar_user'       => $row->rev_user,
                                'ar_user_text'  => $row->rev_user_text,
                                'ar_timestamp'  => $row->rev_timestamp,
@@ -2854,12 +2860,13 @@ class WikiPage implements Page, IDBAccessObject {
                                'ar_page_id'    => $id,
                                'ar_deleted'    => $suppress ? $bitfield : $row->rev_deleted,
                                'ar_sha1'       => $row->rev_sha1,
-                       ];
+                       ] + $arCommentStore->insert( $dbw, $comment );
                        if ( $wgContentHandlerUseDB ) {
                                $rowInsert['ar_content_model'] = $row->rev_content_model;
                                $rowInsert['ar_content_format'] = $row->rev_content_format;
                        }
                        $rowsInsert[] = $rowInsert;
+                       $revids[] = $row->rev_id;
                }
                // Copy them into the archive table
                $dbw->insert( 'archive', $rowsInsert, __METHOD__ );
@@ -2874,6 +2881,9 @@ class WikiPage implements Page, IDBAccessObject {
                // Now that it's safely backed up, delete it
                $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
                $dbw->delete( 'revision', [ 'rev_page' => $id ], __METHOD__ );
+               if ( $wgCommentTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
+               }
 
                // Log the deletion, if the page was suppressed, put it in the suppression log instead
                $logtype = $suppress ? 'suppress' : 'delete';
index f0f1f5f..3d26262 100644 (file)
@@ -582,14 +582,14 @@ class CoreParserFunctions {
        }
        public static function talkspace( $parser, $title = null ) {
                $t = Title::newFromText( $title );
-               if ( is_null( $t ) || !$t->canTalk() ) {
+               if ( is_null( $t ) || !$t->canHaveTalkPage() ) {
                        return '';
                }
                return str_replace( '_', ' ', $t->getTalkNsText() );
        }
        public static function talkspacee( $parser, $title = null ) {
                $t = Title::newFromText( $title );
-               if ( is_null( $t ) || !$t->canTalk() ) {
+               if ( is_null( $t ) || !$t->canHaveTalkPage() ) {
                        return '';
                }
                return wfUrlencode( $t->getTalkNsText() );
@@ -632,14 +632,14 @@ class CoreParserFunctions {
        }
        public static function fullpagename( $parser, $title = null ) {
                $t = Title::newFromText( $title );
-               if ( is_null( $t ) || !$t->canTalk() ) {
+               if ( is_null( $t ) || !$t->canHaveTalkPage() ) {
                        return '';
                }
                return wfEscapeWikiText( $t->getPrefixedText() );
        }
        public static function fullpagenamee( $parser, $title = null ) {
                $t = Title::newFromText( $title );
-               if ( is_null( $t ) || !$t->canTalk() ) {
+               if ( is_null( $t ) || !$t->canHaveTalkPage() ) {
                        return '';
                }
                return wfEscapeWikiText( $t->getPrefixedURL() );
@@ -688,14 +688,14 @@ class CoreParserFunctions {
        }
        public static function talkpagename( $parser, $title = null ) {
                $t = Title::newFromText( $title );
-               if ( is_null( $t ) || !$t->canTalk() ) {
+               if ( is_null( $t ) || !$t->canHaveTalkPage() ) {
                        return '';
                }
                return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() );
        }
        public static function talkpagenamee( $parser, $title = null ) {
                $t = Title::newFromText( $title );
-               if ( is_null( $t ) || !$t->canTalk() ) {
+               if ( is_null( $t ) || !$t->canHaveTalkPage() ) {
                        return '';
                }
                return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() );
index 3f0e38f..988e248 100644 (file)
@@ -2296,11 +2296,8 @@ class Parser {
                                                $this->mOutput->addLanguageLink( $nt->getFullText() );
                                        }
 
-                                       /**
-                                        * Strip the whitespace interwiki links produce, see T10897
-                                        */
                                        $s = rtrim( $s . $prefix );
-                                       $s .= rtrim( $trail, "\n" );
+                                       $s .= trim( $trail, "\n" ) == '' ? '' : $prefix . $trail;
                                        continue;
                                }
 
@@ -2325,11 +2322,7 @@ class Parser {
                                                continue;
                                        }
                                } elseif ( $ns == NS_CATEGORY ) {
-                                       /**
-                                        * Strip the whitespace Category links produce, see T2087
-                                        */
-                                       $s = rtrim( $s . $prefix ); # T2087, T87753
-                                       $s .= rtrim( $trail, "\n" );
+                                       $s = rtrim( $s . "\n" ); # T2087
 
                                        if ( $wasblank ) {
                                                $sortkey = $this->getDefaultSort();
@@ -2341,6 +2334,11 @@ class Parser {
                                        $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
                                        $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
 
+                                       /**
+                                        * Strip the whitespace Category links produce, see T2087
+                                        */
+                                       $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
+
                                        continue;
                                }
                        }
@@ -2586,7 +2584,7 @@ class Parser {
                                ) ) );
                                break;
                        case 'talkpagename':
-                               if ( $this->mTitle->canTalk() ) {
+                               if ( $this->mTitle->canHaveTalkPage() ) {
                                        $talkPage = $this->mTitle->getTalkPage();
                                        $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
                                } else {
@@ -2594,7 +2592,7 @@ class Parser {
                                }
                                break;
                        case 'talkpagenamee':
-                               if ( $this->mTitle->canTalk() ) {
+                               if ( $this->mTitle->canHaveTalkPage() ) {
                                        $talkPage = $this->mTitle->getTalkPage();
                                        $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
                                } else {
@@ -2694,12 +2692,12 @@ class Parser {
                                $value = $this->mTitle->getNamespace();
                                break;
                        case 'talkspace':
-                               $value = $this->mTitle->canTalk()
+                               $value = $this->mTitle->canHaveTalkPage()
                                        ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
                                        : '';
                                break;
                        case 'talkspacee':
-                               $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
+                               $value = $this->mTitle->canHaveTalkPage() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
                                break;
                        case 'subjectspace':
                                $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
index ddea695..10ba83f 100644 (file)
@@ -89,7 +89,9 @@ class IRCColourfulRCFeedFormatter implements RCFeedFormatter {
                        ) );
                        $flag = $attribs['rc_log_action'];
                } else {
-                       $comment = self::cleanupForIRC( $attribs['rc_comment'] );
+                       $comment = self::cleanupForIRC(
+                               CommentStore::newKey( 'rc_comment' )->getComment( $attribs )->text
+                       );
                        $flag = '';
                        if ( !$attribs['rc_patrolled']
                                && ( $wgUseRCPatrol || $attribs['rc_type'] == RC_NEW && $wgUseNPPatrol )
index 047d6cf..998c695 100644 (file)
@@ -102,8 +102,9 @@ class RevDelLogItem extends RevDelItem {
                // User links and action text
                $action = $formatter->getActionText();
                // Comment
+               $comment = CommentStore::newKey( 'log_comment' )->getComment( $this->row )->text;
                $comment = $this->list->getLanguage()->getDirMark()
-                       . Linker::commentBlock( $this->row->log_comment );
+                       . Linker::commentBlock( $comment );
 
                if ( LogEventsList::isDeleted( $this->row, LogPage::DELETED_COMMENT ) ) {
                        $comment = '<span class="history-deleted">' . $comment . '</span>';
@@ -135,7 +136,7 @@ class RevDelLogItem extends RevDelItem {
                }
                if ( LogEventsList::userCan( $this->row, LogPage::DELETED_COMMENT, $user ) ) {
                        $ret += [
-                               'comment' => $this->row->log_comment,
+                               'comment' => CommentStore::newKey( 'log_comment' )->getComment( $this->row )->text,
                        ];
                }
 
index 1932778..ceb97e4 100644 (file)
@@ -63,7 +63,11 @@ class RevDelLogList extends RevDelList {
        public function doQuery( $db ) {
                $ids = array_map( 'intval', $this->ids );
 
-               return $db->select( 'logging', [
+               $commentQuery = CommentStore::getKey( 'log_comment' )->getJoin();
+
+               return $db->select(
+                       [ 'logging' ] + $commentQuery['tables'],
+                       [
                                'log_id',
                                'log_type',
                                'log_action',
@@ -73,13 +77,13 @@ class RevDelLogList extends RevDelList {
                                'log_namespace',
                                'log_title',
                                'log_page',
-                               'log_comment',
                                'log_params',
                                'log_deleted'
-                       ],
+                       ] + $commentQuery['fields'],
                        [ 'log_id' => $ids ],
                        __METHOD__,
-                       [ 'ORDER BY' => 'log_id DESC' ]
+                       [ 'ORDER BY' => 'log_id DESC' ],
+                       $commentQuery['joins']
                );
        }
 
index 52db51a..acd5d14 100644 (file)
@@ -598,7 +598,9 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                [
                                        'maxDays' => (int)$this->getConfig()->get( 'RCMaxAge' ) / ( 24 * 3600 ), // Translate to days
                                        'limitArray' => $this->getConfig()->get( 'RCLinkLimits' ),
+                                       'limitDefault' => $this->getDefaultLimit(),
                                        'daysArray' => $this->getConfig()->get( 'RCLinkDays' ),
+                                       'daysDefault' => $this->getDefaultDays(),
                                ]
                        );
                }
@@ -902,6 +904,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                $opts->add( 'invert', false );
                $opts->add( 'associated', false );
                $opts->add( 'urlversion', 1 );
+               $opts->add( 'tagfilter', '' );
 
                return $opts;
        }
@@ -1153,6 +1156,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                &$join_conds, FormOptions $opts
        ) {
                $dbr = $this->getDB();
+               $isStructuredUI = $this->isStructuredFilterUiEnabled();
 
                foreach ( $this->filterGroups as $filterGroup ) {
                        // URL parameters can be per-group, like 'userExpLevel',
@@ -1162,7 +1166,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                        $query_options, $join_conds, $opts[$filterGroup->getName()] );
                        } else {
                                foreach ( $filterGroup->getFilters() as $filter ) {
-                                       if ( $opts[$filter->getName()] ) {
+                                       if ( $filter->isActive( $opts, $isStructuredUI ) ) {
                                                $filter->modifyQuery( $dbr, $this, $tables, $fields, $conds,
                                                        $query_options, $join_conds );
                                        }
@@ -1535,4 +1539,8 @@ abstract class ChangesListSpecialPage extends SpecialPage {
        protected function isStructuredFilterUiEnabled() {
                return $this->getUser()->getOption( 'rcenhancedfilters' );
        }
+
+       abstract function getDefaultLimit();
+
+       abstract function getDefaultDays();
 }
index ede4898..61590d7 100644 (file)
@@ -299,7 +299,7 @@ class SpecialNewpages extends IncludableSpecialPage {
         */
        protected function revisionFromRcResult( stdClass $result ) {
                return new Revision( [
-                       'comment' => $result->rc_comment,
+                       'comment' => CommentStore::newKey( 'rc_comment' )->getComment( $result )->text,
                        'deleted' => $result->rc_deleted,
                        'user_text' => $result->rc_user_text,
                        'user' => $result->rc_user,
index 4659b9d..547a1b0 100644 (file)
@@ -241,7 +241,6 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                $opts->add( 'categories', '' );
                $opts->add( 'categories_any', false );
-               $opts->add( 'tagfilter', '' );
 
                return $opts;
        }
@@ -636,7 +635,21 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                $message = $this->msg( 'recentchangestext' )->inContentLanguage();
                if ( !$message->isDisabled() ) {
-                       $content = $message->parse();
+                       // Parse the message in this weird ugly way to preserve the ability to include interlanguage
+                       // links in it (T172461). In the future when T66969 is resolved, perhaps we can just use
+                       // $message->parse() instead. This code is copied from Message::parseText().
+                       $parserOutput = MessageCache::singleton()->parse(
+                               $message->plain(),
+                               $this->getPageTitle(),
+                               /*linestart*/true,
+                               // Message class sets the interface flag to false when parsing in a language different than
+                               // user language, and this is wiki content language
+                               /*interface*/false,
+                               $wgContLang
+                       );
+                       $content = $parserOutput->getText();
+                       // Add only metadata here (including the language links), text is added below
+                       $this->getOutput()->addParserOutputMetadata( $parserOutput );
 
                        $langAttributes = [
                                'lang' => $wgContLang->getHtmlCode(),
@@ -991,4 +1004,12 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
        protected function getCacheTTL() {
                return 60 * 5;
        }
+
+       function getDefaultLimit() {
+               return $this->getUser()->getIntOption( 'rclimit' );
+       }
+
+       function getDefaultDays() {
+               return $this->getUser()->getIntOption( 'rcdays' );
+       }
 }
index 862863a..ba3cb87 100644 (file)
@@ -104,6 +104,10 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                                'wgStructuredChangeFiltersSavedQueriesPreferenceName',
                                'rcfilters-wl-saved-queries'
                        );
+                       $output->addJsConfigVars(
+                               'wgStructuredChangeFiltersEditWatchlistUrl',
+                               SpecialPage::getTitleFor( 'EditWatchlist' )->getLocalURL()
+                       );
                }
        }
 
@@ -144,6 +148,40 @@ class SpecialWatchlist extends ChangesListSpecialPage {
        protected function registerFilters() {
                parent::registerFilters();
 
+               // legacy 'extended' filter
+               $this->registerFilterGroup( new ChangesListBooleanFilterGroup( [
+                       'name' => 'extended-group',
+                       'filters' => [
+                               [
+                                       'name' => 'extended',
+                                       'isReplacedInStructuredUi' => true,
+                                       'activeValue' => false,
+                                       'default' => $this->getUser()->getBoolOption( 'extendwatchlist' ),
+                                       'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables,
+                                                                                                 &$fields, &$conds, &$query_options, &$join_conds ) {
+                                               $nonRevisionTypes = [ RC_LOG ];
+                                               Hooks::run( 'SpecialWatchlistGetNonRevisionTypes', [ &$nonRevisionTypes ] );
+                                               if ( $nonRevisionTypes ) {
+                                                       $conds[] = $dbr->makeList(
+                                                               [
+                                                                       'rc_this_oldid=page_latest',
+                                                                       'rc_type' => $nonRevisionTypes,
+                                                               ],
+                                                               LIST_OR
+                                                       );
+                                               }
+                                       },
+                               ]
+                       ],
+
+               ] ) );
+
+               if ( $this->isStructuredFilterUiEnabled() ) {
+                       $this->getFilterGroup( 'lastRevision' )
+                               ->getFilter( 'hidepreviousrevisions' )
+                               ->setDefault( !$this->getUser()->getBoolOption( 'extendwatchlist' ) );
+               }
+
                $this->registerFilterGroup( new ChangesListStringOptionsFilterGroup( [
                        'name' => 'watchlistactivity',
                        'title' => 'rcfilters-filtergroup-watchlistactivity',
@@ -236,7 +274,6 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $user = $this->getUser();
 
                $opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
-               $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
                $opts->add( 'limit', $user->getIntOption( 'wllimit' ), FormOptions::INT );
 
                return $opts;
@@ -300,8 +337,11 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        // unchecked boxes.
                        foreach ( $this->filterGroups as $filterGroup ) {
                                if ( $filterGroup instanceof ChangesListBooleanFilterGroup ) {
+                                       /** @var ChangesListBooleanFilter $filter */
                                        foreach ( $filterGroup->getFilters() as $filter ) {
-                                               $allBooleansFalse[$filter->getName()] = false;
+                                               if ( $filter->displaysOnUnstructuredUi() ) {
+                                                       $allBooleansFalse[$filter->getName()] = false;
+                                               }
                                        }
                                }
                        }
@@ -343,29 +383,9 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $dbr = $this->getDB();
                $user = $this->getUser();
 
-               # Toggle watchlist content (all recent edits or just the latest)
-               if ( !$opts['extended'] ) {
-                       # Top log Ids for a page are not stored
-                       $nonRevisionTypes = [ RC_LOG ];
-                       Hooks::run( 'SpecialWatchlistGetNonRevisionTypes', [ &$nonRevisionTypes ] );
-                       if ( $nonRevisionTypes ) {
-                               $conds[] = $dbr->makeList(
-                                       [
-                                               'rc_this_oldid=page_latest',
-                                               'rc_type' => $nonRevisionTypes,
-                                       ],
-                                       LIST_OR
-                               );
-                       }
-               }
-
                $tables = array_merge( [ 'recentchanges', 'watchlist' ], $tables );
                $fields = array_merge( RecentChange::selectFields(), $fields );
 
-               $query_options = array_merge( [
-                       'ORDER BY' => 'rc_timestamp DESC',
-                       'LIMIT' => $opts['limit']
-               ], $query_options );
                $join_conds = array_merge(
                        [
                                'watchlist' => [
@@ -402,13 +422,14 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        ], LIST_OR );
                }
 
+               $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
                ChangeTags::modifyDisplayQuery(
                        $tables,
                        $fields,
                        $conds,
                        $join_conds,
                        $query_options,
-                       ''
+                       $tagFilter
                );
 
                $this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts );
@@ -417,6 +438,23 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        return false;
                }
 
+               $orderByAndLimit = [
+                       'ORDER BY' => 'rc_timestamp DESC',
+                       'LIMIT' => $opts['limit']
+               ];
+               if ( in_array( 'DISTINCT', $query_options ) ) {
+                       // ChangeTags::modifyDisplayQuery() adds DISTINCT when filtering on multiple tags.
+                       // In order to prevent DISTINCT from causing query performance problems,
+                       // we have to GROUP BY the primary key. This in turn requires us to add
+                       // the primary key to the end of the ORDER BY, and the old ORDER BY to the
+                       // start of the GROUP BY
+                       $orderByAndLimit['ORDER BY'] = 'rc_timestamp DESC, rc_id DESC';
+                       $orderByAndLimit['GROUP BY'] = 'rc_timestamp, rc_id';
+               }
+               // array_merge() is used intentionally here so that hooks can, should
+               // they so desire, override the ORDER BY / LIMIT condition(s)
+               $query_options = array_merge( $orderByAndLimit, $query_options );
+
                return $dbr->select(
                        $tables,
                        $fields,
@@ -795,21 +833,29 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $showUpdatedMarker = $this->getConfig()->get( 'ShowUpdatedMarker' );
 
                // Show watchlist header
-               $form .= "<p>";
+               $watchlistHeader = '';
                if ( $numItems == 0 ) {
-                       $form .= $this->msg( 'nowatchlist' )->parse() . "\n";
+                       $watchlistHeader = $this->msg( 'nowatchlist' )->parse();
                } else {
-                       $form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
+                       $watchlistHeader .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
                        if ( $this->getConfig()->get( 'EnotifWatchlist' )
                                && $user->getOption( 'enotifwatchlistpages' )
                        ) {
-                               $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
+                               $watchlistHeader .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
                        }
                        if ( $showUpdatedMarker ) {
-                               $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
+                               $watchlistHeader .= $this->msg(
+                                       $this->isStructuredFilterUiEnabled() ?
+                                               'rcfilters-watchlist-showupdated' :
+                                               'wlheader-showupdated'
+                               )->parse() . "\n";
                        }
                }
-               $form .= "</p>";
+               $form .= Html::rawElement(
+                       'div',
+                       [ 'class' => 'watchlistDetails' ],
+                       $watchlistHeader
+               );
 
                if ( $numItems > 0 && $showUpdatedMarker ) {
                        $form .= Xml::openElement( 'form', [ 'method' => 'post',
@@ -860,4 +906,12 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $count = $store->countWatchedItems( $this->getUser() );
                return floor( $count / 2 );
        }
+
+       function getDefaultLimit() {
+               return $this->getUser()->getIntOption( 'wllimit' );
+       }
+
+       function getDefaultDays() {
+               return $this->getUser()->getIntOption( 'watchlistdays' );
+       }
 }
index 51e446d..2206be8 100644 (file)
@@ -173,6 +173,7 @@ class BlockListPager extends TablePager {
                                break;
 
                        case 'ipb_reason':
+                               $value = CommentStore::newKey( 'ipb_reason' )->getComment( $row )->text;
                                $formatted = Linker::formatComment( $value );
                                break;
 
@@ -208,8 +209,10 @@ class BlockListPager extends TablePager {
        }
 
        function getQueryInfo() {
+               $commentQuery = CommentStore::newKey( 'ipb_reason' )->getJoin();
+
                $info = [
-                       'tables' => [ 'ipblocks', 'user' ],
+                       'tables' => [ 'ipblocks', 'user' ] + $commentQuery['tables'],
                        'fields' => [
                                'ipb_id',
                                'ipb_address',
@@ -217,7 +220,6 @@ class BlockListPager extends TablePager {
                                'ipb_by',
                                'ipb_by_text',
                                'by_user_name' => 'user_name',
-                               'ipb_reason',
                                'ipb_timestamp',
                                'ipb_auto',
                                'ipb_anon_only',
@@ -229,9 +231,9 @@ class BlockListPager extends TablePager {
                                'ipb_deleted',
                                'ipb_block_email',
                                'ipb_allow_usertalk',
-                       ],
+                       ] + $commentQuery['fields'],
                        'conds' => $this->conds,
-                       'join_conds' => [ 'user' => [ 'LEFT JOIN', 'user_id = ipb_by' ] ]
+                       'join_conds' => [ 'user' => [ 'LEFT JOIN', 'user_id = ipb_by' ] ] + $commentQuery['joins']
                ];
 
                # Filter out any expired blocks
index 43d7ad4..38a332e 100644 (file)
@@ -69,14 +69,17 @@ class DeletedContribsPager extends IndexPager {
                                ' != ' . Revision::SUPPRESSED_USER;
                }
 
+               $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
+
                return [
-                       'tables' => [ 'archive' ],
+                       'tables' => [ 'archive' ] + $commentQuery['tables'],
                        'fields' => [
-                               'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp', 'ar_comment',
+                               'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp',
                                'ar_minor_edit', 'ar_user', 'ar_user_text', 'ar_deleted'
-                       ],
+                       ] + $commentQuery['fields'],
                        'conds' => $conds,
-                       'options' => [ 'USE INDEX' => $index ]
+                       'options' => [ 'USE INDEX' => [ 'archive' => $index ] ],
+                       'join_conds' => $commentQuery['joins'],
                ];
        }
 
@@ -253,7 +256,7 @@ class DeletedContribsPager extends IndexPager {
                $rev = new Revision( [
                        'title' => $page,
                        'id' => $row->ar_rev_id,
-                       'comment' => $row->ar_comment,
+                       'comment' => CommentStore::newKey( 'ar_comment' )->getComment( $row )->text,
                        'user' => $row->ar_user,
                        'user_text' => $row->ar_user_text,
                        'timestamp' => $row->ar_timestamp,
index 47b059b..813d1d4 100644 (file)
@@ -244,7 +244,9 @@ class ImageListPager extends TablePager {
                $prefix = $table === 'oldimage' ? 'oi' : 'img';
 
                $tables = [ $table ];
-               $fields = array_keys( $this->getFieldNames() );
+               $fields = $this->getFieldNames();
+               unset( $fields['img_description'] );
+               $fields = array_keys( $fields );
 
                if ( $table === 'oldimage' ) {
                        foreach ( $fields as $id => &$field ) {
@@ -264,6 +266,13 @@ class ImageListPager extends TablePager {
 
                $options = $join_conds = [];
 
+               # Description field
+               $commentQuery = CommentStore::newKey( $prefix . '_description' )->getJoin();
+               $tables += $commentQuery['tables'];
+               $fields += $commentQuery['fields'];
+               $join_conds += $commentQuery['joins'];
+               $fields['description_field'] = "'{$prefix}_description'";
+
                # Depends on $wgMiserMode
                # Will also not happen if mShowAll is true.
                if ( isset( $this->mFieldNames['count'] ) ) {
@@ -497,6 +506,8 @@ class ImageListPager extends TablePager {
                        case 'img_size':
                                return htmlspecialchars( $this->getLanguage()->formatSize( $value ) );
                        case 'img_description':
+                               $field = $this->mCurrentRow->description_field;
+                               $value = CommentStore::newKey( $field )->getComment( $this->mCurrentRow )->text;
                                return Linker::formatComment( $value );
                        case 'count':
                                return $this->getLanguage()->formatNum( intval( $value ) + 1 );
index dafd244..53362d9 100644 (file)
@@ -90,15 +90,17 @@ class NewPagesPager extends ReverseChronologicalPager {
                        $conds['page_is_redirect'] = 0;
                }
 
+               $commentQuery = CommentStore::newKey( 'rc_comment' )->getJoin();
+
                // Allow changes to the New Pages query
-               $tables = [ 'recentchanges', 'page' ];
+               $tables = [ 'recentchanges', 'page' ] + $commentQuery['tables'];
                $fields = [
                        'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text',
-                       'rc_comment', 'rc_timestamp', 'rc_patrolled', 'rc_id', 'rc_deleted',
+                       'rc_timestamp', 'rc_patrolled', 'rc_id', 'rc_deleted',
                        'length' => 'page_len', 'rev_id' => 'page_latest', 'rc_this_oldid',
                        'page_namespace', 'page_title'
-               ];
-               $join_conds = [ 'page' => [ 'INNER JOIN', 'page_id=rc_cur_id' ] ];
+               ] + $commentQuery['fields'];
+               $join_conds = [ 'page' => [ 'INNER JOIN', 'page_id=rc_cur_id' ] ] + $commentQuery['joins'];
 
                // Avoid PHP 7.1 warning from passing $this by reference
                $pager = $this;
index 823b5da..20b44d2 100644 (file)
@@ -236,6 +236,7 @@ class ProtectedPagesPager extends TablePager {
                                                LogPage::DELETED_COMMENT,
                                                $this->getUser()
                                        ) ) {
+                                               $value = CommentStore::newKey( 'log_comment' )->getComment( $row )->text;
                                                $formatted = Linker::formatComment( $value !== null ? $value : '' );
                                        } else {
                                                $formatted = $this->msg( 'rev-deleted-comment' )->escaped();
@@ -284,8 +285,10 @@ class ProtectedPagesPager extends TablePager {
                        $conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace );
                }
 
+               $commentQuery = CommentStore::newKey( 'log_comment' )->getJoin();
+
                return [
-                       'tables' => [ 'page', 'page_restrictions', 'log_search', 'logging' ],
+                       'tables' => [ 'page', 'page_restrictions', 'log_search', 'logging' ] + $commentQuery['tables'],
                        'fields' => [
                                'pr_id',
                                'page_namespace',
@@ -297,9 +300,8 @@ class ProtectedPagesPager extends TablePager {
                                'pr_cascade',
                                'log_timestamp',
                                'log_user',
-                               'log_comment',
                                'log_deleted',
-                       ],
+                       ] + $commentQuery['fields'],
                        'conds' => $conds,
                        'join_conds' => [
                                'log_search' => [
@@ -312,7 +314,7 @@ class ProtectedPagesPager extends TablePager {
                                                'ls_log_id = log_id'
                                        ]
                                ]
-                       ]
+                       ] + $commentQuery['joins']
                ];
        }
 
index 7d697a1..f5367bb 100644 (file)
@@ -287,7 +287,7 @@ class UploadFromUrl extends UploadBase {
 
                wfDebugLog( 'fileupload', $status );
                if ( $status->isOK() ) {
-                       wfDebugLog( 'fileupload', 'Download by URL completed successfuly.' );
+                       wfDebugLog( 'fileupload', 'Download by URL completed successfully.' );
                } else {
                        wfDebugLog(
                                'fileupload',
diff --git a/languages/i18n/ais.json b/languages/i18n/ais.json
new file mode 100644 (file)
index 0000000..c3662c9
--- /dev/null
@@ -0,0 +1,591 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Akamycoco",
+                       "Benel",
+                       "Bunukwiki",
+                       "Tokoabibi"
+               ]
+       },
+       "sunday": "pilipayan",
+       "monday": "sakacacay a demied nu lipay",
+       "tuesday": "sakatusa a demied nu lipay",
+       "wednesday": "sakatulu a demied nu lipay",
+       "thursday": "sakasepat a demied nu lipay",
+       "friday": "sakalima a demied nu lipay",
+       "saturday": "sakaenem a demied nu lipay",
+       "sun": "pilipayan",
+       "mon": "sakacacay a demied nu lipay",
+       "tue": "sakatusa a demied nu lipay",
+       "wed": "sakatulu a demied nu lipay",
+       "thu": "sakasepat a demied nu lipay",
+       "fri": "sakalima a demied nu lipay",
+       "sat": "sakaenem a demied nu lipay",
+       "january": "cacay bulad",
+       "february": "tusa bulad",
+       "march": "tulu bulad",
+       "april": "sepat bulad",
+       "may_long": "lima bulad",
+       "june": "enem bulad",
+       "july": "pitu bulad",
+       "august": "walu bulad",
+       "september": "siwa bulad",
+       "october": "cacay bataan bulad",
+       "november": "sabaw cacay bulad",
+       "december": "sabaw tusa bulad",
+       "january-gen": "cacay bulad",
+       "february-gen": "tusa bulad",
+       "march-gen": "tulu bulad",
+       "april-gen": "sepat bulad",
+       "may-gen": "lima bulad",
+       "june-gen": "enem bulad",
+       "july-gen": "pitu bulad",
+       "august-gen": "walu bulad",
+       "september-gen": "siwa bulad",
+       "october-gen": "cacay bataan bulad",
+       "november-gen": "sabaw cacay bulad",
+       "december-gen": "sabaw tusa bulad",
+       "jan": "cacay bulad",
+       "feb": "tusa bulad",
+       "mar": "tulu bulad",
+       "apr": "sepat bulad",
+       "may": "lima bulad",
+       "jun": "enem bulad",
+       "jul": "pitu bulad",
+       "aug": "walu bulad",
+       "sep": "siwa bulad",
+       "oct": "cacay bataan bulad",
+       "nov": "sabaw cacay bulad",
+       "dec": "sabaw tusa bulad",
+       "january-date": "cacay bulad $1",
+       "february-date": "tusa bulad $1",
+       "march-date": "tulu bulad $1",
+       "april-date": "sepat bulad $1",
+       "may-date": "lima bulad $1",
+       "june-date": "enem bulad $1",
+       "july-date": "pitu bulad $1",
+       "august-date": "walu bulad $1",
+       "september-date": "siwa bulad $1",
+       "october-date": "cacay bataan bulad $1",
+       "november-date": "sabaw cacay bulad $1",
+       "december-date": "sabaw tusa bulad $1",
+       "period-am": "AM",
+       "period-pm": "PM",
+       "pagecategories": "{{PLURAL:$1|kakuniza}}",
+       "category_header": "abuay nu kasasizuma \"$1\" a kasabelih",
+       "subcategories": "sailuc-kasasizuma",
+       "category-media-header": "kakuniza labuay \"$1\" a myiti",
+       "hidden-categories": "{{PLURAL:$1|midimut kakuniza}}",
+       "category-subcat-count": "{{PLURAL:$2|uyni kakuniza hatiza ku cacay yamalyilu sailuc-kakuniza. kina kakuniza yamalyilu isasa $2 a sailuc-kasasizuma, ilabu {{PLURAL:$1}}mahiza ku isasaay}}",
+       "category-article-count": "{{PLURAL:$2|uyni kakuniza hatiza ku cacay yamalyilu sailuc-kakuniza. kina kakuniza yamalyilu isasa $2 a sailuc-kasasizuma, ilabu {{PLURAL:$1}}mahiza ku isasaay}}",
+       "category-file-count": "{{PLURAL:$2|kakuniza yamalyilu isasaay a cacay ku tangan. kakuniza yamalyilu isasaay izaway $1 ku tangan, pulung $2 makalaan.}}",
+       "listingcontinuesabbrev": "palalid",
+       "about": "mahizaay u",
+       "newwindow": "(paynin baluhay a azih-sasingalan miwawah)",
+       "cancel": "palawpes",
+       "moredotdotdot": "yadah...",
+       "mypage": "kasabelih",
+       "mytalk": "sasukamu",
+       "anontalk": "sasukamu",
+       "navigation": "pasubana’ tu miidangay",
+       "and": "&#32;",
+       "namespaces": "pangangananay a salaedan",
+       "variants": "masazumaay",
+       "navigation-heading": "pasubana’ tu miidangay pipili’an",
+       "returnto": "tatiku tazuma至 $1.",
+       "tagline": "makayzaay i {{SITENAME}}",
+       "help": "buhci tu kamu",
+       "search": "kilim",
+       "searchbutton": "kilim",
+       "go": "mileku",
+       "searcharticle": "mileku",
+       "history": "kasabelih nazipa’an",
+       "history_short": "nazipa’an",
+       "history_small": "nazipa’an",
+       "printableversion": "kapah tu insace baziyong / sapad",
+       "permalink": "saluimengay misiket",
+       "view": "ciwsace",
+       "view-foreign": "i $1 miciwsace",
+       "edit": "mikawaway tu kalumyiti",
+       "create": "patizeng",
+       "create-local": "cunusen itiniay a buhci tu kamu",
+       "newpage": "baluhayay a kasabelih",
+       "talkpagelinktext": "sasukamu",
+       "personaltools": "teked sakaluk",
+       "talk": "matatengil",
+       "views": "ciwsace",
+       "toolbox": "sakaluk",
+       "otherlanguages": "zumaay a kamu",
+       "redirectedfrom": "(miliyawtu tazuma nay $1)",
+       "redirectpagesub": "miliyaw tazuma kasabelih",
+       "redirectto": "miliyaw tazuma tu:",
+       "lastmodifiedat": "uyni kasabelih sazikuz mikawaway tu kalumyiti i $1 $2.",
+       "jumpto": "taayaw:",
+       "jumptonavigation": "pasubana’ tu miidangay",
+       "jumptosearch": "kilim",
+       "aboutsite": "mahizaay u {{SITENAME}}",
+       "aboutpage": "Project:mahizaay u...",
+       "copyright": "anu izaw ku zuma buhci tu kamu, kasabelih aazihen a lacul i labu, pisaungay hamin $1 sapabeli tu kinli a ceding.",
+       "copyrightpage": "{{ns:project}}:nisanga’an niza tu tungus a kawaw",
+       "currentevents": "ayzaay a sinbun",
+       "currentevents-url": "Project:Current events",
+       "disclaimers": "caay pimuku tu sikining",
+       "disclaimerpage": "Project:habutud a pimuku tu sikining",
+       "edithelp": "mikawaway tu kalumyiti buhci tu kamu",
+       "helppage-top-gethelp": "buhci tu kamu",
+       "mainpage": "saayaway a belih",
+       "mainpage-description": "saayaway a belih",
+       "policy-url": "Project:Policy",
+       "portal": "komiyonityi sacumudan",
+       "portal-url": "Project:komiyonityi sacumudan",
+       "privacy": "salimek a mikuwanay a kawaw",
+       "privacypage": "Project:salimek a mikuwanay a kawaw",
+       "retrievedfrom": "miala i \"$1\"",
+       "editsection": "mikawaway tu kalumyiti",
+       "editold": "mikawaway tu kalumyiti",
+       "viewsourceold": "ciwsace sakatizeng bangu",
+       "editlink": "mikawaway tu kalumyiti",
+       "viewsourcelink": "ciwsace sakatizeng bangu",
+       "editsectionhint": "mikawaway tu kalumyiti tusil: $1",
+       "toc": "dilyikotoling",
+       "showtoc": "paazih",
+       "hidetoc": "midimut",
+       "collapsible-expand": "micuwat",
+       "confirmable-yes": "hang",
+       "confirmable-no": "caay",
+       "site-atom-feed": "$1 a Atom saangangan",
+       "page-atom-feed": "$1 a Atom saangangan",
+       "red-link-title": "$1 (kasabelih nayai’ tu)",
+       "nstab-main": "kasabelih",
+       "nstab-user": "misaungayay a kasabelih",
+       "nstab-special": "sazumaay a kasabelih",
+       "nstab-project": "cwanan kasabelih",
+       "nstab-image": "tangan",
+       "nstab-template": "taazihan mitudung",
+       "nstab-category": "kakuniza",
+       "mainpage-nstab": "saayaway a belih",
+       "badtitle": "a’cusay a pyawti",
+       "badtitletext": "matuzu’ay a kasabelih pyawti u la’cusay、nayi’ ku cacan, caaysa tatenga’ay tu misiket kamu Wikiay a pyawti.\ntebanay pyawti akay amalyilu la’cusay pisaungay i pyawtayi a tatebanan nu nisulitan.",
+       "viewsource": "ciwsace sakatizeng bangu",
+       "userlogin-yourname": "misaungayay a kalungangan",
+       "userlogin-yourname-ph": "pisulitan tu nu misay a misaungayay a kalungangan",
+       "yourpassword": "mima:",
+       "userlogin-yourpassword": "mima",
+       "userlogin-yourpassword-ph": "suliten nu misuay a mima",
+       "createacct-yourpassword-ph": "pisulitan ku mima",
+       "createacct-yourpasswordagain": "malucekay tu mima",
+       "createacct-yourpasswordagain-ph": "pisulitan ku mima kinacacay aca",
+       "userlogin-remembermypassword": "i balucu’en aku patalabu setyitase",
+       "login": "patalabu",
+       "nav-login-createaccount": "patalabu / panganganen ku canghaw",
+       "logout": "katahkal",
+       "userlogin-noaccount": "inayi’ ku canghaw kisu haw?",
+       "userlogin-joinproject": "micunus {{SITENAME}}",
+       "createaccount": "panganganen ku canghaw",
+       "userlogin-resetpassword-link": "maliyuh ku mima kisu haw?",
+       "userlogin-helplink2": "patalabu miedap",
+       "createacct-emailoptional": "imyiyo(email) tigami (u pili’ay sasulitan)",
+       "createacct-email-ph": "pisulitan ku imyiyo(email) nu misu",
+       "createacct-submit": "panganganen ku misuay a canghaw",
+       "createacct-another-submit": "panganganen ku canghaw",
+       "createacct-benefit-heading": "{{SITENAME}} paanin tu nisulitan tu nu tapangay mahiza kisuan.",
+       "createacct-benefit-body1": "saka{{PLURAL:$1|ku mikawaway tu kalumyiti}}",
+       "createacct-benefit-body2": "{{PLURAL:$1| kasabelih}}",
+       "createacct-benefit-body3": "cay katenesay{{PLURAL:$1|paaninay tu kalusasing}}",
+       "loginlanguagelabel": "kamu: $1",
+       "pt-login": "patalabu",
+       "pt-login-button": "patalabu",
+       "pt-createaccount": "panganganen ku canghaw",
+       "pt-userlogout": "katahkal",
+       "botpasswords-label-cancel": "palawpes",
+       "resetpass-submit-cancel": "palawpes",
+       "passwordreset": "miliyaw miteka setin mima",
+       "changeemail-none": "(nayi’)",
+       "bold_sample": "kibetulay a sulit",
+       "bold_tip": "kibetulay a sulit",
+       "italic_sample": "tukenihay nisulit",
+       "italic_tip": "tukenihay nisulit",
+       "link_sample": "misiket satangahan a sulit",
+       "link_tip": "labu-labu misiket",
+       "extlink_sample": "http://www.example.com misiket satangahan a sulit",
+       "extlink_tip": "hekal-hekal misiket (amana katawal saka http:// paangangan)",
+       "headline_sample": "sakacacay a selal nu satangahan a sulit",
+       "headline_tip": "sakatusa a selal nu satangahan a sulit",
+       "nowiki_sample": "pacucuk caayay kesehwaay a cudad",
+       "nowiki_tip": "sekipo Wiki kesehwa sulit nu kamu",
+       "image_tip": "nicunusay a tangan",
+       "media_tip": "tangan-tangan misiket",
+       "sig_tip": "misuay a sulit nu ngangan atu demiad, tuki",
+       "hr_tip": "Sapisasuala (cayka yadah kawiza)",
+       "summary": "pecu’ nu lacul:",
+       "minoredit": "payni mikilulay a mikawaway tu kalumyiti",
+       "watchthis": "miazih tuyni kasabelih",
+       "savearticle": "misuped kasabelih",
+       "preview": "paazih pataayaway miazih",
+       "showpreview": "paazih pataayaway miazih",
+       "showdiff": "paazih ku masumaday",
+       "anoneditwarning": "<strong>patalaw:</strong>caay henay kisu patalabu. anu miteka mikawaway tu kalumyiti, IP adolyise nu misu ama mitilak. anu kisu <strong>[$1  patalabu ]</strong> acasa <strong>[$2 panganganen ku canghaw ]</strong>, misuay mikawaway tu kalumyiti payni tu nu misuay misaungayay kalungangan sacuzu’ ,izaway zuma kapahayay.",
+       "loginreqlink": "patalabu",
+       "newarticletext": "masasiket kisu tu nayi’ay tu kasabelih.\namipatizeng tina kasabelih, kaisasa mikawaway tu kalumyiti atilad misulit ku lacul (kahica nu kawaw piazih tu tatenga’ay [$1 misaungay a buhci tu kamu  kasabelih ]).\namahica caay padeteng tayza tina kasabelih kisu haw, pihaymaw sapecec saazihay a <strong>tatiku</strong> pipenecan.",
+       "noarticletext": "kina kasabelih inayi’ lacul ayza,kapah tu kisu i zumaay a kasabelih [[Special:Search/{{PAGENAME}}| mikilim kina kasabelih pyawti ]]、<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}  mikilim sasuala nasulitan nakawawan ] caay sa[{{fullurl:{{FULLPAGENAME}}|action=edit}} patizeng kina kasabelih ]</span>.",
+       "noarticletext-nopermission": "tina kasabelih ayza inayi’ lacul,\nkapah tu kisu i zuma kasabelih [[Special:Search/{{PAGENAME}}| kilim kina kasabelih pyawti ]],acasa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}  kilim sasuala nasulitan nakawawan ]</span>,uyzasa  inayi’  ku tungus patizeng tina kasabelih.",
+       "editing": "mikawaway tu kalumyiti  $1 ayza",
+       "creating": "patizeng ayza $1",
+       "editingsection": "mikawaway tu kalumyiti ayza $1 (tusil)",
+       "templatesused": "uyni kasabelih pisaungay tu isasaay {{PLURAL:$1|taazihan mitudung}}:",
+       "template-protected": "(madiputay)",
+       "template-semiprotected": "(madiputay a kasabelih - satizep mikawaway tu kalumyiti)",
+       "hiddencategories": "kina kasabelih tungusay nu {{PLURAL:$1|1 midimut kakuniza }}mamikawaw:",
+       "permissionserrorstext-withaction": "namakay isasaay {{PLURAL:$1|mahicaay}}, inayi’ kisu situngus miteka $2 miteka tuway misaungay:",
+       "moveddeleted-notice": "kina kasabelih masipu tu.\nisasa nipabeli kina kasabelihay a masipu atu milimad nasulitan nakawawan, taneng miazih tu tatenga’ay.",
+       "viewpagelogs": "ciwsace kina kasabelih a nasulitan nakawawan",
+       "currentrev-asof": "i $1 a sabaluhay masumad",
+       "revisionasof": "$1 a sumad",
+       "revision-info": "i $1 tuyni a {{GENDER:$6|$2}} u nasumad nu nikawawan",
+       "previousrevision": "← masumad nu ayaway",
+       "nextrevision": "kilulay masumad →",
+       "currentrevisionlink": "sabaluhay masumad",
+       "cur": "ayza",
+       "last": "ayaway",
+       "rev-showdeleted": "paazih",
+       "revdelete-show-file-submit": "hang",
+       "revdelete-radio-set": "midimut",
+       "history-title": "\"$1\" masumaday a nazipa’an",
+       "difference-title": "\"$1\" misumad laeday sasizuma",
+       "lineno": "silsil $1:",
+       "editundo": "patiku",
+       "diff-multi-sameuser": "(malecaday misaungayay {{PLURAL:$1| ilaed izaw ku $1 a sumad}}inayi’ paazih)",
+       "searchresults": "heci nu makatepa",
+       "searchresults-title": "$1 heci nu makatepa",
+       "prevn": "ayaw saka {{PLURAL:$1|$1}}",
+       "nextn": "zikuzan saka {{PLURAL:$1|$1}}",
+       "nextn-title": "nuzikuzan saka {{PLURAL:$1|a heci}}",
+       "shown-title": "paybelih {{PLURAL:$1|$1 ku heci}} paazih",
+       "viewprevnext": "ciwsace ($1 {{int:pipe-separator}} $2) ($3)",
+       "searchmenu-new": "<strong> uyni Wiki patizeng ku kasabelih  \"[[:$1]]\"!</strong>{{PLURAL:$2|0=| acasa miazih tu tatenga’ay patalabuay a matepa’ay a heci. |acasa miazih tu tatenga’ay zuma matepa’ay a heci.}}",
+       "searchprofile-articles": "lacul kasabelih",
+       "searchprofile-images": "malocimyidiya",
+       "searchprofile-everything": "hamin",
+       "searchprofile-advanced": "tapabaw",
+       "searchprofile-articles-tooltip": "i labu nu $1 mikilim",
+       "searchprofile-images-tooltip": "mikilim tu tangan",
+       "searchprofile-everything-tooltip": "kilim saca hamin lacul (yamalyilu sasukamu kasabelih)",
+       "searchprofile-advanced-tooltip": "mikilim pakuniza misanga’ pangangananay a salaedan",
+       "search-result-size": "$1 ({{PLURAL:$2|$2 ku sulit}})",
+       "search-redirect": "(miliyaw tazuma namakay $1)",
+       "search-section": "(tusil $1)",
+       "search-suggest": "u tuzu’ nu misu ku:$1 haw?",
+       "searchall": "hamin",
+       "search-showingresults": "{{PLURAL:$4|saka <strong>$1</strong> a heci, pulung <strong>$3</strong>|saka <strong>$1-$2</strong> a heci, pulung <strong>$3</strong>}}",
+       "search-nonefound": "nayi’ matatungusay palalitemuh tu kawaw maheciay.",
+       "powersearch-toggleall": "hamin",
+       "powersearch-togglenone": "nayi’",
+       "mypreferences": "setin tu kanamuhan",
+       "skin-preview": "paazih pataayaway miazih",
+       "prefs-rc": "capi a demaiday a sumad",
+       "prefs-editing": "mikawaway tu kalumyiti",
+       "searchresultshead": "kilim",
+       "stub-threshold-disabled": "mapasatezep",
+       "prefs-searchoptions": "kilim",
+       "yourlanguage": "kamu:",
+       "prefs-editor": "mikawaway tu kalumyiti",
+       "prefs-preview": "paazih pataayaway miazih",
+       "group-all": "(hamin)",
+       "right-writeapi": "pisaungay suliten API",
+       "grant-createaccount": "panganganen ku canghaw",
+       "newuserlogpage": "patizeng misaungayay nasulitan nakawawan",
+       "action-edit": "mikawaway tu kalumyiti uyni kasabelih",
+       "enhancedrc-history": "nazipa’an",
+       "recentchanges": "capi a demaiday a sumad",
+       "recentchanges-legend": "capi a demiad masumaday a mapiliay",
+       "recentchanges-summary": "mikilul nazikuzan ilabu nu Wikiay a kasabelihay a capi demiad a nasumaday.",
+       "recentchanges-label-newpage": "uyni mikawaway tu kalumyiti patizengtu baluhay kasabelih",
+       "recentchanges-label-minor": "payni mikilulay a mikawaway tu kalumyiti",
+       "recentchanges-label-bot": "uyni mikawaway tu kalumyiti u kikay a tademaw mileku",
+       "recentchanges-label-unpatrolled": "mikawaway tu kalumyiti caay henay ka tayza mikibi",
+       "recentchanges-label-plusminus": "na kasabelih misumad hacica ku tabaki (wyiyincu )",
+       "recentchanges-legend-heading": "<strong>u tinaku nu kulit:</strong>",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (piazih tu tatenga’ay [[Special:NewPages| baluhayay a kasabelih]])",
+       "recentchanges-submit": "paazih",
+       "rcfilters-savedqueries-cancel-label": "palawpes",
+       "rcfilters-filterlist-title": "kilim",
+       "rclistfrom": "paazih nay $3 $2 baluhayay a sumad katukuh ayza",
+       "rcshowhideminor": "$1 mikilulay mikawaway tu kalumyiti",
+       "rcshowhideminor-show": "paazih",
+       "rcshowhideminor-hide": "Hide",
+       "rcshowhidebots": "$1 kikay a tademaw",
+       "rcshowhidebots-show": "paazih",
+       "rcshowhidebots-hide": "midimut",
+       "rcshowhideliu": "$1 mapangangan tuway a misaungayay",
+       "rcshowhideliu-show": "paazih",
+       "rcshowhideliu-hide": "midimut",
+       "rcshowhideanons": "$1 paceba panganganay a misaungayay",
+       "rcshowhideanons-show": "paazih",
+       "rcshowhideanons-hide": "midimut",
+       "rcshowhidepatr-show": "paazih",
+       "rcshowhidepatr-hide": "midimut",
+       "rcshowhidemine": "$1 mikawaway tu kalumyiti nu maku",
+       "rcshowhidemine-show": "paazih",
+       "rcshowhidemine-hide": "midimut",
+       "rcshowhidecategorization-show": "paazih",
+       "rcshowhidecategorization-hide": "midimut",
+       "rclinks": "paazih capi a demiad $2 a demiaday a saka $1 a sumad.",
+       "diff": "sasizuma",
+       "hist": "nazipa’an",
+       "hide": "midimut",
+       "show": "paazih",
+       "minoreditletter": "adidi’",
+       "newpageletter": "baluhay",
+       "boteditletter": "kikay a tademaw",
+       "rc-change-size-new": "masumadtu sa u $1 {{PLURAL:$1|wyiyincu}}",
+       "recentchangeslinked": "sasuala a sumad",
+       "recentchangeslinked-feed": "sasuala a sumad",
+       "recentchangeslinked-toolbox": "sasuala a sumad",
+       "recentchangeslinked-title": "pulung \"$1\" sasuala a sumaday",
+       "recentchangeslinked-summary": "uyni kasabelih pasilsil micuzu’ kasabelih masasiketay saca hamin kasabelihan (hakya u matuzu’ay kakuniza\nilabu saca hamin mamikawaw)izaw ku  misumaday kasabelih piazihan tu sulit.\nizawtu ku [[Special:Watchlist|sapacukat a sulit nu misu]] ilabuay a kasabelih apatahkal ku <strong>kibetulay a sulit</strong> sacuzu’.",
+       "recentchangeslinked-page": "kasabelih kalungangan:",
+       "recentchangeslinked-to": "Show changes to pages linked to the given page instead\nmisumad ku paazih masasiket tayza matuzu’ay kasabelih a nisumad",
+       "upload": "patapabaw ku tangan",
+       "filedesc": "pecu’ nu lacul",
+       "upload-dialog-button-cancel": "palawpes",
+       "upload-form-label-infoform-description": "patahkal",
+       "upload-form-label-infoform-categories": "kakuniza",
+       "upload-form-label-infoform-date": "demiad",
+       "license": "sapabeli tu kinli a cedang",
+       "license-header": "sapabeli tu kinli a cedang",
+       "imgfile": "tangan",
+       "listfiles": "tangan-tangan misiket",
+       "listfiles_date": "demiad",
+       "listfiles_description": "patahkal",
+       "listfiles-latestversion-yes": "hang",
+       "listfiles-latestversion-no": "caay",
+       "file-anchor-link": "tangan",
+       "filehist": "tangan nu nazipa’an",
+       "filehist-help": "sapecec ku demiad/tuki sapiciwsace ku tuki nina demiad a tangan baziyong",
+       "filehist-current": "ayza",
+       "filehist-datetime": "demiad/tuki",
+       "filehist-thumb": "sukep tu zunga",
+       "filehist-thumbtext": "nu $1 baziyongay a sukep tu zunga",
+       "filehist-user": "misaungayay",
+       "filehist-dimensions": "ditek",
+       "filehist-comment": "pacunus sakacaay kapawan",
+       "imagelinks": "tangan sadama tu kawaw",
+       "linkstoimage": "isasaay {{PLURAL:$1| kasabelih  misiket |saka $1 a kasabelih misiket}}katukuh tina tangan:",
+       "nolinkstoimage": "nayi’ ku kasabelih masasiket katukuh tini a tangan.",
+       "sharedupload-desc-here": "kina tangan nay $1 hakay satu pisaungay tu zuma a cwanan.\nisasaay paazih kuyniay a tangan i [$2 tangan patahkal kasabelih] a patahkalay a lacul.",
+       "upload-disallowed-here": "la’cus kisu mitahpu tuyni a tangan.",
+       "randompage": "kakibalucu’ ay a kasabelih",
+       "randomincategory-submit": "mileku",
+       "pageswithprop-submit": "mileku",
+       "withoutinterwiki-submit": "paazih",
+       "nbytes": "$1 {{PLURAL:$1|wyiyincu}}",
+       "ncategories": "{{PLURAL:$1|kakuniza}}",
+       "nmembers": "$1 {{PLURAL:$1|ku mamikawaw}}",
+       "prefixindex-submit": "paazih",
+       "protectedpages-page": "kasabelih",
+       "newpages": "baluhay kasabelih",
+       "newpages-submit": "paazih",
+       "move": "milimad",
+       "pager-older-n": "{{PLURAL:$1| kusa malumanay}}",
+       "suppress": "malangat",
+       "apisandbox-continue": "palalid",
+       "booksources": "nu cudad atu laculaculan",
+       "booksources-search-legend": "mikilim ku cudad atu laculaculan",
+       "booksources-search": "kilim",
+       "log": "nasulitan nakawawan",
+       "logeventslist-submit": "paazih",
+       "checkbox-all": "hamin",
+       "checkbox-none": "nayi’",
+       "allpages": "hamin nu kasabelih",
+       "allarticles": "hamin nu kasabelih",
+       "allpagessubmit": "mileku",
+       "categories": "kakuniza",
+       "categories-submit": "paazih",
+       "linksearch-ok": "kilim",
+       "listusers-submit": "paazih",
+       "watchlist": "miazihay a piazihan tu sulit",
+       "mywatchlist": "miazihay a piazihan tu sulit",
+       "watch": "miazih",
+       "watchlist-hide": "midimut",
+       "historyaction-submit": "paazih",
+       "dellogpage": "masipu ku nasulitan nakawawan",
+       "rollbacklink": "panukasan",
+       "rollbacklinkcount": "patiku {{PLURAL:$1|mikawaway tu kalumyiti}}",
+       "protectlogpage": "midiput nasulitan nakawawan",
+       "restriction-edit": "mikawaway tu kalumyiti",
+       "restriction-move": "milimad",
+       "undelete-search-submit": "kilim",
+       "undelete-show-file-submit": "hang",
+       "namespace": "pangangananay a salaedan:",
+       "invert": "kabelihan mipili’",
+       "tooltip-invert": "pili’en hatizaay kuyni mapili’ay atilad i midimut mipili’panganganan a salaedan blabuay kasabelih sumad (anu hatizaay sasuala panganganan a salaedan, mama palecad midimut sasuala panganganan a salaedan)",
+       "namespace_association": "sasuala panganganan a salaedan",
+       "tooltip-namespace_association": "pili’en hatizaay kuyni mapili’ay atilad i yamalyilu atu mipili’ sasuala panganganan a salaedan sasukamu atu satangahan panganganan a salaedan",
+       "blanknamespace": "(sausi)",
+       "contributions": "{{GENDER:$1| misaungayay}}paanin tu nisulitan",
+       "mycontris": "paanin",
+       "anoncontribs": "paanin",
+       "uctop": "(ayza)",
+       "month": "sazikuzay demiad nabuladan:",
+       "year": "sazikuzay demiad mihcaan:",
+       "sp-contributions-blocklog": "milangat tu nasulitan nakawawan",
+       "sp-contributions-logs": "nasulitan nakawawan",
+       "sp-contributions-talk": "sasukamu",
+       "sp-contributions-submit": "kilim",
+       "whatlinkshere": "masasiket katukuh uyniyay a kasabelih",
+       "whatlinkshere-title": "masasiket tazumaay a \"$1\" kasabelih",
+       "whatlinkshere-page": "Kasabelih:",
+       "linkshere": "isasaay a kasabelih masasiket tazuma tu <strong>[[:$1]]</strong>:",
+       "isredirect": "miliyaw tazuma kasabelih",
+       "istemplate": "nicaliwan",
+       "isimage": "tangan-tangan misiket",
+       "whatlinkshere-prev": "saka {{PLURAL:$1|nuayaway}}",
+       "whatlinkshere-next": "saka {{PLURAL:$1|nuzikuzan}}",
+       "whatlinkshere-links": "← masasiket",
+       "whatlinkshere-hideredirs": "$1 miliyaw tazuma",
+       "whatlinkshere-hidetrans": "$1 nicaliwan",
+       "whatlinkshere-hidelinks": "$1 masasiket",
+       "whatlinkshere-hideimages": "$1 tangan-tangan misiket",
+       "whatlinkshere-filters": "kilim",
+       "whatlinkshere-submit": "mileku",
+       "autoblocklist-submit": "kilim",
+       "ipblocklist-submit": "kilim",
+       "blocklink": "malangat",
+       "contribslink": "paanin",
+       "blocklogpage": "milangat tu nasulitan nakawawan",
+       "movelogpage": "milimad ku nasulitan nakawawan",
+       "export": "patahkal ku kasabelih",
+       "allmessages-filter-legend": "kilim",
+       "allmessages-filter-all": "hamin",
+       "allmessages-language": "kamu:",
+       "allmessages-filter-submit": "mileku",
+       "thumbnail-more": "patabaki(micuwat)",
+       "tooltip-pt-userpage": "{{GENDER:|misaungayay nu misu}} kasabelih",
+       "tooltip-pt-mytalk": "{{GENDER:|misuay }}sasukamu a kasabelih",
+       "tooltip-pt-preferences": "{{GENDER:|misuay}} setin tu kanamuhan",
+       "tooltip-pt-watchlist": "miazih kisu misumad kasabelih piazihan tu sulit ayza",
+       "tooltip-pt-mycontris": "{{GENDER:|misuay}}paaninay a piazihan tu sulit",
+       "tooltip-pt-login": "patahkal nizateng kisu ayaw patalabu, uyza sa kapah tu amana.",
+       "tooltip-pt-logout": "katahkal",
+       "tooltip-pt-createaccount": "taneng kami pauuh kisuan panganganen tu cacay canghaw atu patalabu, anu caay ku tabakiay a pisaungay.",
+       "tooltip-ca-talk": "matatengil tu mahizaay lacul nu kasabelih",
+       "tooltip-ca-edit": "mikawaway tu kalumyiti uyni kasabelih",
+       "tooltip-ca-viewsource": "uyni kasabelih madiputay tuway.\nkapah kisu miciwsace tuyni kasabelih sakatizeng bangu",
+       "tooltip-ca-history": "uyini kasabelih nasawniay a sumad",
+       "tooltip-ca-move": "milimad tina kasabelih",
+       "tooltip-ca-watch": "paynien kasabelih micunus misuay cyinse piazihan tu sulit",
+       "tooltip-search": "kilim {{SITENAME}}",
+       "tooltip-search-go": "amahica milihiza tuyni kalungangan malalanepay kasabelih izaay tu, taayawen ku tiza kasabelih",
+       "tooltip-search-fulltext": "mikilim pisaungay tina paycudadcudad a kasabelih",
+       "tooltip-p-logo": "taayaw saayaway a belih",
+       "tooltip-n-mainpage": "taayaw saayaway a belih",
+       "tooltip-n-mainpage-description": "taayaw saayaway a belih",
+       "tooltip-n-portal": "mahizaay uyni a cwanan, amihica kisu kapah tu、icuwa amatepa maydihay a kawaw atu duut nu misu",
+       "tooltip-n-currentevents": "i labu nu sinbun a kawaw, matepa sasuala tada kalunasulitan",
+       "tooltip-n-recentchanges": "pasilsil tuyni Wiki labuay a sapisumaday a piazihan tu sulit",
+       "tooltip-n-randompage": "kakibalucu’ ay micumud cacay a kasabelih",
+       "tooltip-n-help": "mamiedapay a kakitizaan",
+       "tooltip-t-whatlinkshere": "pasilsil saca hamin masasiket uyni kasabelihay a kasabelih",
+       "tooltip-t-recentchangeslinked": "uyni kasabelih masasiket tayza i zuma  kasabelih capi demiaday a sumad",
+       "tooltip-feed-atom": "uyni a kasabelih nu Atom saanganga",
+       "tooltip-t-contributions": "{{GENDER:$1| tina misaungayay}} a paanin tu nisulitan piazihan tu sulit",
+       "tooltip-t-upload": "patapabaw ku tangan",
+       "tooltip-t-specialpages": "hamin sazumaay kasabelih piazihan tu sulit",
+       "tooltip-t-print": "uyni kasabelihay a taneng insace a baziyong",
+       "tooltip-t-permalink": "uyni kasabelih masumaday saluimengay misiket",
+       "tooltip-ca-nstab-main": "ciwsace kasabelihay a lacul",
+       "tooltip-ca-nstab-user": "ciwsace misaungayay a kasabelih",
+       "tooltip-ca-nstab-special": "uyni kasabelih u sazumaay belih, cayka tineng mikawaway tu kalumyiti",
+       "tooltip-ca-nstab-project": "ciwsace cwanan kasabelih",
+       "tooltip-ca-nstab-image": "ciwsace tangan kasabelih",
+       "tooltip-ca-nstab-template": "ciwsace taazihan mitudung",
+       "tooltip-ca-nstab-category": "ciwsace kakuniza a kasabelih",
+       "tooltip-save": "misuped misuay a pisumad",
+       "tooltip-preview": "kay iayaw nu pisuped miazih tu nu misuay nisumad.",
+       "tooltip-diff": "paazih hica sa kisu labuay a nisumadan",
+       "tooltip-watch": "paynien kasabelih micunus misuay cyinse piazihan tu sulit",
+       "tooltip-rollback": "sapecec \"patiku\" misiket, kapah patiku tayza nuayawanay a paanin tu nisulitan tina kasabelih mikawaway tu kalumyiti",
+       "tooltip-undo": "\"patiku\" kapah tu patiku tuyni mikawaway tu kalumyiti payni pataayaway miazih muse miwawah mikawaway tu kalumyiti aazihan cudad, saka caay cayaw nu labu micunus mahicaay.",
+       "tooltip-summary": "kapisulitan apuyu’ay a pecu’ nu lacul",
+       "simpleantispam-label": "sapi tena’ babakahen a sulit kinsa.\nyu <strong>amana</strong> misulit kuyni pisinga’an!",
+       "pageinfo-toolboxlink": "kasabelih cesyun",
+       "pageinfo-contentpage-yes": "hang",
+       "pageinfo-protect-cascading-yes": "hang",
+       "previousdiff": "← malumanay a mikawaway tu kalumyiti",
+       "nextdiff": "baluhayay mikawaway tu kalumyit →",
+       "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|ku kasabelih}}",
+       "file-info-size": "$1 × $2 syangsu, hacica ku tabaki nu tangan: $3, MIME kakuniza: $4",
+       "file-info-size-pages": "$1 × $2 syangsu, hacica ku tabaki nu tangan:$3,MIME kakuniza: $4, $5 {{PLURAL:$5|ku kasabelih}}",
+       "file-nohires": "inay ku sangaleb takalaway a katingalaw, kapah tu nipabeli.",
+       "svg-long-desc": "SVG tangan, maazihay hacica ku tabaki $1 × $2  syangsu, tangan hacica ku tabaki: $3",
+       "show-big-image": "saayaway a tangan",
+       "show-big-image-preview": "pataayaway miazih hacica ku tabaki: $1.",
+       "show-big-image-size": "$1 × $2 syangsu",
+       "newimages-legend": "kilim",
+       "ilsubmit": "kilim",
+       "metadata": "pulita tu kalunasulitan",
+       "metadata-help": "uyni tangan labuay amin yamalyilu zuma cesyun, uyni a cesyun akay nay suewyi  sasasing asaca sapisekyin i tapang asaca suwyihwa saayaw katukuh pahzekan a nakawawan mapacunusay. anu nay tangan saayaway setyitase masumadtu, hatizaay pulita kalunasulitan akay la’cus mileku mabetil a misumad tu tangan.",
+       "metadata-fields": "i tini palatuh patahkal i labuay a EXIF pulita tu kalunasulitanay a kakitizaan, yamalyilu i zunga paazih kasabelih, sapipulita tu cudad nu nasulitan malepi’ paazih palatuh.\nzumaay a pulita tu cudad pataayaw tu kawaw midimut.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "exif-orientation": "papayzaan",
+       "exif-xresolution": "sasuala katingalaw",
+       "exif-yresolution": "mitelekay katingalaw",
+       "exif-datetime": "tangan misumaday a demiad atu tuki",
+       "exif-make": "sasasing misanga’ay tu kalutuud",
+       "exif-model": "sasasing silosi nu nisanga’an",
+       "exif-software": "pisaungay zwanti",
+       "exif-artist": "masacudaday",
+       "exif-exifversion": "Exif baziyong / sapad",
+       "exif-colorspace": "kalukulit salaedan",
+       "exif-datetimeoriginal": "kalunasulitan masangaay’ a demiad atu tuki",
+       "exif-datetimedigitized": "suwyihwaay a demiad atu tuki",
+       "exif-languagecode": "kamu",
+       "exif-iimcategory": "kakuniza",
+       "exif-orientation-1": "tatungus",
+       "exif-gaincontrol-0": "nayi’",
+       "exif-contrast-0": "tatungus",
+       "exif-contrast-1": "pu’nel",
+       "exif-contrast-2": "takalaw",
+       "exif-saturation-0": "tatungus",
+       "exif-sharpness-0": "tatungus",
+       "exif-sharpness-1": "pu’nel",
+       "exif-sharpness-2": "takalaw",
+       "exif-dc-date": "demiad",
+       "exif-urgency-normal": "tatungus ($1)",
+       "namespacesall": "hamin",
+       "monthsall": "hamin",
+       "imgmultigo": "mileku!",
+       "img-lang-go": "mileku",
+       "table_pager_limit_submit": "mileku",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1| sasukamu ]])",
+       "version-specialpages": "sazumaay a kasabelih",
+       "version-ext-colheader-description": "patahkal",
+       "version-ext-colheader-credits": "masacudaday",
+       "version-libraries-description": "patahkal",
+       "version-libraries-authors": "masacudaday",
+       "redirect-submit": "mileku",
+       "fileduplicatesearch-submit": "kilim",
+       "specialpages": "sazumaay a kasabelih",
+       "specialpages-group-login": "patalabu / panganganen ku canghaw",
+       "tag-filter": "[[Special:Tags|aazihen paya]] kilim:",
+       "tag-filter-submit": "kilim",
+       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|aazihen paya’}}]]: $2)",
+       "tags-active-yes": "hang",
+       "tags-active-no": "caay",
+       "tags-deactivate": "mapasatezep",
+       "permanentlink": "saluimengay misiket",
+       "htmlform-no": "caay",
+       "htmlform-yes": "hang",
+       "htmlform-cloner-delete": "milimad",
+       "logentry-delete-delete": "$1 {{GENDER:$2|masiputu}} kasabelih $3",
+       "restore-count-files": "{{PLURAL:$1|1 tangan}}",
+       "logentry-move-move": "$1 {{GENDER:$2|milimad tuway}} kasabelih $3 katukuh $4",
+       "logentry-newusers-create": "{{GENDER:$2|patizeng}} misaungayay canghaw tuway $1",
+       "logentry-upload-upload": "$1 {{GENDER:$2|masayacay tuway}} $3",
+       "rightsnone": "(nayi’)",
+       "feedback-cancel": "palawpes",
+       "searchsuggest-search": "kilim {{SITENAME}}",
+       "expand_templates_preview": "paazih pataayaway miazih",
+       "pagelang-language": "kamu",
+       "log-action-filter-all": "hamin",
+       "log-action-filter-block-block": "malangat"
+}
index e608a0c..eef7a45 100644 (file)
        "rcfilters-liveupdates-button-title-on": "إيقاف التحديثات الحية",
        "rcfilters-liveupdates-button-title-off": "عرض التغييرات الجديدة فور حدوثها",
        "rcfilters-watchlist-markSeen-button": "التعليم على كل التغييرات كمرئية",
+       "rcfilters-watchlist-editWatchlist-button": "تعديل قائمة الصفحات المراقبة",
        "rcnotefrom": "بالأسفل {{PLURAL:$5|التغيير|التغييرات}} منذ <strong>$2</strong> (إلى <strong>$1</strong> معروضة).",
        "rclistfromreset": "إعادة ضبط خيار التاريخ",
        "rclistfrom": "أظهر التغييرات بدء من $3 $2",
index 7f5f333..80d2a17 100644 (file)
        "rcfilters-empty-filter": "Няма актыўных фільтраў. Паказаны ўвесь унёсак.",
        "rcfilters-filterlist-title": "Фільтры",
        "rcfilters-filterlist-whatsthis": "Як гэта працуе?",
-       "rcfilters-filterlist-feedbacklink": "Ð\9fакÑ\96нÑ\83Ñ\86Ñ\8c Ð²Ð¾Ð´Ð³Ñ\83к Ð¿Ñ\80а Ð½Ð¾Ð²Ñ\8bÑ\8f (бÑ\8dÑ\82а) фільтры",
+       "rcfilters-filterlist-feedbacklink": "РаÑ\81кажÑ\8bÑ\86е Ð½Ð°Ð¼, Ñ\88Ñ\82о Ð²Ñ\8b Ð´Ñ\83маеÑ\86е Ð¿Ñ\80а Ð³Ñ\8dÑ\82Ñ\8bÑ\8f (новÑ\8bÑ\8f) фільтры",
        "rcfilters-highlightbutton-title": "Вылучыць вынікі",
        "rcfilters-highlightmenu-title": "Абярыце колер",
        "rcfilters-highlightmenu-help": "Абярыце колер для вылучэньня гэтай уласьцівасьці",
        "delete-warning-toobig": "Гэтая старонка мае доўгую гісторыю рэдагаваньняў, больш за $1 {{PLURAL:$1|вэрсію|вэрсіі|вэрсіяў}}.\nЯе выдаленьне можа выклікаць праблемы ў працы базы зьвестак {{GRAMMAR:родны|{{SITENAME}}}}; будзьце асьцярожны.",
        "deleteprotected": "Вы ня можаце выдаліць гэтую старонку, таму што яна абароненая.",
        "deleting-backlinks-warning": "<strong>Увага:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|іншыя старонкі]] ўключаюць або спасылаюцца на старонку, якую вы зьбіраецеся выдаліць.",
+       "deleting-subpages-warning": "<strong>Папярэджаньне:</strong> старонка, якую вы зьбіраецеся выдаліць, мае [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|$1 падстаронку|$1 падстаронкі|$1 падстаронак|51=болей за 50 падстаронак}}]].",
        "rollback": "Адкаціць рэдагаваньні",
        "rollbacklink": "адкат",
        "rollbacklinkcount": "адкаціць $1 {{PLURAL:$1|рэдагаваньне|рэдагаваньні|рэдагаваньняў}}",
        "redirect-lookup": "Шукаць паводле:",
        "redirect-value": "Значэньне:",
        "redirect-user": "Ідэнтыфікатару ўдзельніка",
-       "redirect-page": "Ідэнтыфікатар старонкі",
+       "redirect-page": "Ідэнтыфікатару старонкі",
        "redirect-revision": "Вэрсіі старонкі",
-       "redirect-file": "Ð\86мÑ\8f Ñ\84айла",
+       "redirect-file": "Ð\9dазва Ñ\84айлÑ\83",
        "redirect-logid": "ID журнала",
        "redirect-not-exists": "Значэньне ня знойдзена",
        "fileduplicatesearch": "Пошук дублікатаў файлаў",
        "fileduplicatesearch-noresults": "Файл з назвай «$1» ня знойдзены.",
        "specialpages": "Спэцыяльныя старонкі",
        "specialpages-note-top": "Легенда",
+       "specialpages-note-restricted": "* Звычайныя спэцыяльныя старонкі.\n* <span class=\"mw-specialpagerestricted\">Спэцыяльныя старонкі з абмежаваным доступам.</span>",
        "specialpages-group-maintenance": "Тэхнічныя справаздачы",
        "specialpages-group-other": "Іншыя спэцыяльныя старонкі",
        "specialpages-group-login": "Уваход / стварэньне рахунку",
        "compare-title-not-exists": "Пазначаная старонка не існуе.",
        "compare-revision-not-exists": "Пазначаная вэрсія не існуе.",
        "diff-form": "Адрозьненьні",
+       "diff-form-oldid": "Ідэнтыфікатар старой вэрсіі (неабавязкова)",
        "dberr-problems": "Прабачце! На гэтым сайце ўзьніклі тэхнічныя цяжкасьці.",
        "dberr-again": "Паспрабуйце пачакаць некалькі хвілінаў і абнавіць.",
        "dberr-info": "(Немагчыма злучыцца з базай зьвестак: $1)",
index 36392f9..941f9af 100644 (file)
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (gwelet ivez [[Special:NewPages|roll ar pajennoù nevez]])",
        "recentchanges-submit": "Diskouez",
        "rcfilters-tag-remove": "Dilemel '$1'",
+       "rcfilters-group-results-by-page": "Strollañ an disoc'hoù dre bajenn",
+       "rcfilters-grouping-title": "O strollañ",
        "rcfilters-activefilters": "Siloù oberiant",
-       "rcfilters-quickfilters": "Liammoù prim",
+       "rcfilters-days-title": "Deizioù paseet",
+       "rcfilters-hours-title": "Eurioù paseet",
+       "rcfilters-days-show-days": "($1 {{PLURAL:$1|deiz}})",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|eur}}",
+       "rcfilters-highlighted-filters-list": "Lakaet war wel : $1",
+       "rcfilters-quickfilters": "Siloù enrollet",
+       "rcfilters-quickfilters-placeholder-title": "Liamm ebet enrollet evit c'hoazh",
        "rcfilters-savedqueries-defaultlabel": "Siloù enrollet",
        "rcfilters-savedqueries-rename": "Adenvel",
        "rcfilters-savedqueries-setdefault": "Gweredekaat dre ziouer",
index efa97ee..5c669a0 100644 (file)
@@ -39,7 +39,7 @@
        "tog-enotifminoredits": "Хаам бо зӀе чухул, цхьа жимма а хийцамаш биняхь",
        "tog-enotifrevealaddr": "Гайта сан зlе оцу хаамаш барехь",
        "tog-shownumberswatching": "Гайта декъашхойн терахь, агӀо латийна болу шай тергаме могӀанан юкъа",
-       "tog-oldsig": "Карара куьгтаӀорна:",
+       "tog-oldsig": "Карара хьан куьг:",
        "tog-fancysig": "Шен вики-къастаман куьгтаӀдар (ша шех хьажорг йоцуш)",
        "tog-uselivepreview": "Лелае чехка хьалха хьажар",
        "tog-forceeditsummary": "Дага даийта, нагахь нисйарх лаьцна чохь язйина яцахь",
        "recentchanges-legend-heading": "<strong>Легенда:&nbsp;</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (хьажа кхин [[Special:NewPages|керла агӀонийн могӀа]])",
        "recentchanges-submit": "Гайта",
-       "rcfilters-savedqueries-defaultlabel": "Ӏалашъе луьттург",
+       "rcfilters-other-review-tools": "<strong>Талларан кхин гӀирсаш</strong>",
+       "rcfilters-group-results-by-page": "Карийнарш, агӀонгахь тобанаш ян",
+       "rcfilters-grouping-title": "Тобанаш",
+       "rcfilters-activefilters": "Жигара литтарш",
+       "rcfilters-limit-title": "Гойту хийцамаш",
+       "rcfilters-limit-shownum": "Гайта тӀеххьара {{PLURAL:$1|хийцам}}",
+       "rcfilters-days-title": "ТӀеххьара денош",
+       "rcfilters-hours-title": "ТӀеххьара сахьташ",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|де}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|сахьт}}",
+       "rcfilters-quickfilters": "Ӏалашдина литтарш",
+       "rcfilters-quickfilters-placeholder-description": "Литтаран буламаш (хьайна) Ӏалашбан лаахь а, йуха уьш лелабан лаахь а, тӀетаӀйе \"жигара литтаран\" уллера хьаьрка.",
+       "rcfilters-savedqueries-defaultlabel": "Ӏалашдина литтарш",
        "rcfilters-savedqueries-rename": "ЦӀе хийцар",
        "rcfilters-savedqueries-setdefault": "Ӏадйитаран кеп хӀоттае",
        "rcfilters-savedqueries-unsetdefault": "Ӏадйитаран кеп дӀаяккха",
        "rcfilters-savedqueries-add-new-title": "Ӏалашде литтар нисъяр",
        "rcfilters-restore-default-filters": "Литтарш Ӏадйитаран кепе меттахӀоттае",
        "rcfilters-clear-all-filters": "Ерриге литтарш цӀанъян",
+       "rcfilters-search-placeholder": "Литтаран керла хийцамаш лахар",
        "rcfilters-filterlist-title": "Литтарш",
+       "rcfilters-filterlist-feedbacklink": "Керла (бета) литтарех лаьцна хьайна хеттарг язде",
+       "rcfilters-highlightbutton-title": "Билгалде карийнарш",
+       "rcfilters-highlightmenu-help": "Билгалонан бос харжа",
        "rcfilters-filterlist-noresults": "Литтарш цакарий",
        "rcfilters-filtergroup-authorship": "Нисде авторалла",
        "rcfilters-filter-editsbyself-label": "Хьан дисдарш",
        "rcfilters-filter-editsbyself-description": "Хьан нисдарш.",
        "rcfilters-filter-editsbyother-label": "Кхечу декъашхойн нисдарш",
+       "rcfilters-filtergroup-userExpLevel": "Декъашхочун регистраци а, цуна зеделларг а",
        "rcfilters-filter-user-experience-level-registered-label": "Регистрацийинарш",
+       "rcfilters-filter-user-experience-level-registered-description": "Чубаьлла декъашхой.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Регистрацицайинарш",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Системин чубалаза декъашхой.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Керланиш",
+       "rcfilters-filter-user-experience-level-newcomer-description": "10 нисдар дина а, 4 дийнахь болх бина а регистрацийина декъашхой",
        "rcfilters-filter-user-experience-level-learner-label": "Доьшуш берш",
+       "rcfilters-filtergroup-automated": "Авто-къинхьегам",
        "rcfilters-filter-bots-label": "Бот",
+       "rcfilters-filter-bots-description": "Авто-гӀирсашца дина нисдарш.",
+       "rcfilters-filter-humans-label": "Адам ду (бот яц)",
+       "rcfilters-filter-humans-description": "Декъашхоша дина нисдарш.",
        "rcfilters-filter-patrolled-label": "Патрулйина",
+       "rcfilters-filtergroup-significance": "МаьӀна",
+       "rcfilters-filter-minor-label": "Жим нисдарш",
+       "rcfilters-filter-minor-description": "Авторс жима ду аьлла билгалдина нисдарш.",
+       "rcfilters-filter-major-label": "Гуттарлера нисдарш",
+       "rcfilters-filter-major-description": "Жима санна билгалдаза нисдарш.",
+       "rcfilters-filtergroup-watchlist": "Тергаме могӀаман юкъара агӀонаш",
+       "rcfilters-filter-watchlist-watched-label": "Тергаме могӀамехь",
+       "rcfilters-filter-watchlist-watched-description": "Хьан тергаме могӀамехь болу хийцамаш.",
+       "rcfilters-filter-watchlist-watchednew-label": "Тергаме могӀаман керла хийцамаш",
+       "rcfilters-filter-watchlist-watchednew-description": "Хьан тергаме могӀаман юкъара хьуна гина боцу хийцамаш.",
+       "rcfilters-filter-watchlist-notwatched-label": "Тергаме могӀаман юкъахь яц",
+       "rcfilters-filter-watchlist-notwatched-description": "Ерриге, хьан тергаме могӀаман юкъахь ерш ца гойту.",
+       "rcfilters-filtergroup-changetype": "Хийцамийн тайпа",
+       "rcfilters-filter-pageedits-label": "АгӀонан нисдарш",
+       "rcfilters-filter-pageedits-description": "Дийцарийн а, категорийн а чулацаман дина нисдарш...",
+       "rcfilters-filter-newpages-label": "АгӀонаш кхоллар",
+       "rcfilters-filter-newpages-description": "АгӀонаш кхуллуш дина нисдарш.",
+       "rcfilters-filter-categorization-label": "Категорийн хийцамаш",
+       "rcfilters-filter-categorization-description": "Категорийн, агӀонаш тӀетохар а, дӀаяхар а.",
+       "rcfilters-filter-logactions-label": "Урхаллица динарш",
+       "rcfilters-filter-logactions-description": "Куьйгалхоша динарг, дӀабазбалар, агӀонаш дӀаяхар, файлаш чуяхар...",
        "rcfilters-filtergroup-lastRevision": "Карара верси",
        "rcfilters-filter-lastrevision-label": "Карара верси",
        "rcfilters-filter-lastrevision-description": "АгӀона уггаре тӀеххьара хийцамаш.",
        "rcfilters-filter-previousrevision-label": "Хьалхара версеш",
+       "rcfilters-filter-previousrevision-description": "Дерриге нисдарш, «тӀеххьара верси» йоцу.",
        "rcfilters-filter-excluded": "ДӀаяьккхина",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:not</strong> $1",
+       "rcfilters-view-advanced-filters-label": "Шуьйра литтарш",
        "rcfilters-view-tags": "Билгалонаш",
+       "rcfilters-view-namespaces-tooltip": "Меттигийн цӀерашца литтаран карийнарш",
+       "rcfilters-view-tags-tooltip": "Нисдарийн билгало йолу литтаран карийна хийцамаш",
        "rcnotefrom": "Лахахь гайтина тӀера <strong>$2</strong> (хийцамаш <strong>$1</strong> кӀезиг).",
        "rclistfromreset": "Терахь харжар дӀадаккха",
        "rclistfrom": "Гайта хийцам {{CURRENTYEAR}} шеран {{CURRENTDAY}} {{CURRENTMONTHNAMEGEN}} {{CURRENTTIME}} бина болу",
        "uctop": "(карара)",
        "month": "Баттачохь (я хьалхе):",
        "year": "Шерачохь (я хьалхе):",
-       "sp-contributions-newbies": "Ð\93айÑ\82а Ð±ÐµÐºÑ\8a ÐºÑ\8aинÑ\85Ñ\8cегам, ÐºÐµÑ\80ла Ð´lабазбинаÑ\87аÑ\80а Ð±Ð¸Ð½Ð° Ð±Ð¾Ð»Ñ\83",
+       "sp-contributions-newbies": "Ð\9aеÑ\80ла Ð´ÐµÐºÑ\8aаÑ\88Ñ\85ойн ÐºÑ\8aинÑ\85Ñ\8cегам Ð±ÐµÐ½ Ð¼Ð° Ð³Ð°Ð¹Ñ\82а",
        "sp-contributions-newbies-sub": "Керла декъашхойн дӀаяздаршкара",
        "sp-contributions-newbies-title": "Дукху хан йоцуш кхоьллинчу декъашхойн дӀаяздарийн къинхьегам",
        "sp-contributions-blocklog": "блоктоьхнарш",
        "newimages-summary": "ХӀокху белхан агӀона чохь гойтуш ю дукха хан йоццуш чуяьхна файлаш.",
        "newimages-legend": "Луьттург",
        "newimages-user": "Декъашхочун цӀе я IP-адрес",
+       "newimages-newbies": "Керла декъашхойн къинхьегам бен ма гайта",
        "newimages-showbots": "Гайта боташ чуяьхна файлаш",
        "newimages-hidepatrolled": "Къайлаяха патруль йина файлаш",
        "newimages-mediatype": "Медиа тайпа:",
        "exif-whitebalance-0": "Къайн автоматически баланс",
        "exif-whitebalance-1": "Куьйга хӀоттийна къайн баланс",
        "exif-scenecapturetype-0": "Стандартан",
+       "exif-scenecapturetype-1": "Ландшафт",
+       "exif-scenecapturetype-2": "Сурт",
        "exif-scenecapturetype-3": "Буса даьккхина сурт",
        "exif-gaincontrol-0": "Яц",
        "exif-gaincontrol-1": "ГӀеххьа доккха дар",
        "htmlform-cloner-create": "ТӀетоха кхин",
        "htmlform-cloner-delete": "ДӀаяккха",
        "htmlform-date-placeholder": "ШШШШ-ББ-ДД",
+       "htmlform-time-placeholder": "СС:ММ:СС",
        "htmlform-datetime-placeholder": "ШШШШ-ББ-ДД СС:ММ:СС",
        "htmlform-title-not-exists": "«$1» яц.",
        "htmlform-user-not-exists": "<strong>$1</strong> яц.",
        "pagelang-language": "Мотт",
        "pagelang-use-default": "Ӏадйитаран кепаца мотт",
        "pagelang-select-lang": "Харжа мотт",
+       "pagelang-reason": "Бахьана",
        "pagelang-submit": "ДӀадахьийта",
+       "pagelang-nonexistent-page": "$1 агӀо яц.",
        "right-pagelang": "АгӀона мотт хийца",
        "action-pagelang": "агӀона мотт хийца",
        "log-name-pagelang": "Мотт хийцаран тептар",
        "date-range-to": "Терхье:",
        "sessionprovider-generic": "$1 сесси",
        "randomrootpage": "Цахууш нисъелла ораман агӀо",
+       "log-action-filter-block-block": "Блоктохар",
+       "log-action-filter-block-reblock": "Блоктохар хийцар",
+       "log-action-filter-block-unblock": "БлокдӀаяхарш",
+       "log-action-filter-rights-autopromote": "Авто хийцар",
+       "log-action-filter-upload-upload": "Керла чудаккхар",
+       "log-action-filter-upload-overwrite": "Юху чуяккха",
+       "authmanager-email-label": "Электронан пошт",
        "authmanager-provider-temporarypassword": "Ханна пароль",
        "changecredentials": "Декъашхочун дӀаяздаран хийцам",
        "removecredentials": "ДӀадаха декъашхойн дӀаяздарш",
index 7435ca6..11d151f 100644 (file)
        "revdelete-restricted": "ئەو سنووری بەرگریانەی خستراوەتە سەر بەڕێوبەران",
        "revdelete-unrestricted": "ئەو سنووری بەرگریانەی لابردراوە لە سەر بەڕێوبەران",
        "logentry-block-block": "$1 {{GENDER:$4|$3}}ی بۆ ماوەی $5 {{GENDER:$2|بەربەست کرد}} $6",
+       "logentry-import-upload": "$1 {{GENDER:$2|بارکرد}} $3 بە بەکارھێنانی [[special:Import|بارکەر]]",
        "logentry-move-move": "$1 پەڕەی $3ی {{GENDER:$2|گواستەوە}} بۆ $4",
        "logentry-move-move-noredirect": "$1 پەڕەی $3ی بە بێ بەجێھشتنی ڕەوانەکەرێک {{GENDER:$2|گواستەوە}} بۆ $4",
        "logentry-move-move_redir": "$1 پەڕەی $3 {{GENDER:$2|گواستەوە}} بۆ $4 کە پێشتر ڕەوانەکەر بوو",
index bf31a15..badda46 100644 (file)
        "rcfilters-empty-filter": "Žádné aktivní filtry. Zobrazeny jsou všechny příspěvky.",
        "rcfilters-filterlist-title": "Filtry",
        "rcfilters-filterlist-whatsthis": "Jak to funguje?",
-       "rcfilters-filterlist-feedbacklink": "Poskytnout novým (beta) filtrům zpětnou vazbu",
+       "rcfilters-filterlist-feedbacklink": "Řekněte nám, co si myslíte o těchto (nových) filtrech",
        "rcfilters-highlightbutton-title": "Zvýraznit výsledky",
        "rcfilters-highlightmenu-title": "Vybrat barvu",
        "rcfilters-highlightmenu-help": "Vyberte barvu pro zvýraznění této vlastnosti",
        "rcfilters-liveupdates-button-title-on": "Vypnout živé aktualizace",
        "rcfilters-liveupdates-button-title-off": "Zobrazovat nové změny, jakmile jsou zveřejněny",
        "rcfilters-watchlist-markSeen-button": "Označit všechny změny jako zkontrolované",
+       "rcfilters-watchlist-editWatchlist-button": "Editovat seznam sledovaných stránek",
+       "rcfilters-watchlist-showupdated": "Změny stránek, které jste od provedení změn nenavštívili, jsou zobrazeny <strong>tučně</strong> s vyplněnou značkou.",
        "rcnotefrom": "Níže {{PLURAL:$5|je změna|jsou změny}} od <strong>$3, $4</strong> ({{PLURAL:$1|zobrazena|zobrazeny|zobrazeno}} nejvýše <strong>$1</strong>).",
        "rclistfromreset": "Obnovit výběr data",
        "rclistfrom": "Ukázat nové změny, počínaje od $2, $3",
        "notvisiblerev": "Verze byla smazána",
        "watchlist-details": "Na vašem seznamu sledovaných stránek {{PLURAL:$1|je $1 stránka|jsou $1 stránky|je $1 stránek}}, nepočítaje v to diskusní stránky.",
        "wlheader-enotif": "Upozorňování e-mailem je zapnuto.",
-       "wlheader-showupdated": "Stránky, které se změnily od vaší poslední návštěvy, jsou zobrazeny '''tučně'''.",
+       "wlheader-showupdated": "Stránky, které se změnily od vaší poslední návštěvy, jsou zobrazeny <strong>tučně</strong>.",
        "wlnote": "Níže {{PLURAL:$1|je poslední změna|jsou poslední <strong>$1</strong> změny|je posledních <strong>$1</strong> změn}} za {{PLURAL:$2|poslední hodinu|poslední <strong>$2</strong> hodiny|posledních <strong>$2</strong> hodin}} do $4, $3.",
        "wlshowlast": "Ukázat posledních $1 hodin $2 dnů",
        "watchlist-hide": "Skrýt",
index 453af88..64307c3 100644 (file)
        "search-external": "Brug anden søgemaskine",
        "searchdisabled": "<p>Beklager! Fuldtekstsøgningen er midlertidigt afbrudt på grund af for stor belastning på serverne. I mellemtidem kan du anvende Google- eller Yahoo!-søgefelterne herunder. Bemærk at deres kopier af {{SITENAME}}s indhold kan være forældet.</p>",
        "search-error": "Der opstod en fejl under søgning: $1",
+       "search-warning": "Der opstod en advarsel under søgning: $1",
        "preferences": "Indstillinger",
        "mypreferences": "Indstillinger",
        "prefs-edits": "Antal redigeringer:",
        "prefs-editwatchlist-clear": "Ryd din overvågningsliste",
        "prefs-watchlist-days": "Antal dage der skal vises i overvågningslisten:",
        "prefs-watchlist-days-max": "Højst $1 {{PLURAL:$1|dag|dage}}",
-       "prefs-watchlist-edits": "Antal redigeringer der vises i udvidet overvågningsliste:",
+       "prefs-watchlist-edits": "Maksimalt antal redigeringer der vises i overvågningsliste:",
        "prefs-watchlist-edits-max": "Maks. 1000",
        "prefs-watchlist-token": "Overvågningslistenøgle:",
        "prefs-misc": "Forskelligt",
        "prefs-editing": "Redigering",
        "searchresultshead": "Søgeresultater",
        "stub-threshold": "Grænse før formatering af stublink ($1):",
+       "stub-threshold-sample-link": "eksempel",
        "stub-threshold-disabled": "Deaktiveret",
        "recentchangesdays": "Antal dage som skal vises i seneste ændringer:",
        "recentchangesdays-max": "(maks. $1 {{PLURAL:$1|dag|dage}})",
        "prefs-help-recentchangescount": "Det gælder for seneste ændringer, historikker og logger.",
        "prefs-help-watchlist-token2": "Dette er den hemmelige nøgle til web-feed af din overvågningsliste.\nHvis andre kender den, vil man være i stand til at læse din overvågningsliste, så del den ikke.\n[[Special:ResetTokens|Klik her hvis du har brug at nulstille den]].",
        "savedprefs": "Dine indstillinger er blevet gemt.",
+       "savedrights": "Brugergrupperne for {{GENDER:$1|$1}} er blevet gemt.",
        "timezonelegend": "Tidszone:",
        "localtime": "Lokaltid:",
        "timezoneuseserverdefault": "Brug wiki'ens standardindstilling ($1)",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (se også [[Special:NewPages|listen over nye sider]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Vis",
+       "rcfilters-tag-remove": "Fjern »$1«",
+       "rcfilters-legend-heading": "<strong>Liste over forkortelser:</strong>",
+       "rcfilters-other-review-tools": "<strong>Andre gennemgangsværktøjer</strong>",
        "rcfilters-group-results-by-page": "Grupper resultater efter side",
        "rcfilters-grouping-title": "Gruppering",
        "rcfilters-activefilters": "Aktive filtre",
        "rcfilters-advancedfilters": "Avancerede filtre",
        "rcfilters-limit-title": "Ændringer som skal vises",
+       "rcfilters-limit-shownum": "Vis seneste {{PLURAL:$1|ændring|$1 ændringer}}",
        "rcfilters-days-title": "De sidste dage",
        "rcfilters-hours-title": "De sidste timer",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|dag|dage}}",
        "rcfilters-filter-lastrevision-description": "Den nyeste ændring af en side.",
        "rcfilters-filter-previousrevision-label": "Tidligere revisioner",
        "rcfilters-filter-previousrevision-description": "Alle ændringer som ikke er den nyeste ændring af en side.",
+       "rcfilters-view-advanced-filters-label": "Avancerede filtre",
+       "rcfilters-view-tags": "Mærkede redigeringer",
+       "rcfilters-view-namespaces-tooltip": "Filtrer resultater efter navnerum",
        "rcnotefrom": "Nedenfor er op til '''$1''' {{PLURAL:$5|ændring|ændringer}} siden '''$2''' vist.",
        "rclistfromreset": "Nulstil datovalg",
        "rclistfrom": "Vis nye ændringer startende fra den $3 kl. $2",
index c4c6984..a719fb6 100644 (file)
@@ -72,6 +72,7 @@
        "yourdomainname": "Ihre Domain:",
        "password-change-forbidden": "Sie können auf diesem Wiki keine Passwörter ändern.",
        "externaldberror": "Entweder liegt ein Fehler bei der externen Authentifizierung vor oder Sie dürfen Ihr externes Benutzerkonto nicht aktualisieren.",
+       "login-security": "Verifizieren Sie Ihre Identität",
        "userlogin-noaccount": "Sie haben noch kein Benutzerkonto?",
        "userlogin-loggedin": "Sie sind bereits als {{GENDER:$1|$1}} angemeldet.\nBenutzen Sie das unten stehende Formular, um sich unter einem anderen Benutzernamen anzumelden.",
        "createacct-email-ph": "Geben Sie Ihre E-Mail-Adresse ein",
index 391963e..0665579 100644 (file)
        "rcfilters-empty-filter": "Keine aktiven Filter. Es werden alle Beiträge angezeigt.",
        "rcfilters-filterlist-title": "Filter",
        "rcfilters-filterlist-whatsthis": "Wie funktioniert das?",
-       "rcfilters-filterlist-feedbacklink": "Rückmeldung zu den neuen (Beta-)Filtern hinterlassen",
+       "rcfilters-filterlist-feedbacklink": "Erzähle uns, was du über diese (neuen) Filterwerkzeuge denkst.",
        "rcfilters-highlightbutton-title": "Ergebnisse hervorheben",
        "rcfilters-highlightmenu-title": "Eine Farbe auswählen",
        "rcfilters-highlightmenu-help": "Eine Farbe auswählen, um diese Eigenschaft hervorzuheben.",
        "rcfilters-liveupdates-button-title-on": "Live-Aktualisierungen ausschalten",
        "rcfilters-liveupdates-button-title-off": "Neue Änderungen bei Auftreten anzeigen",
        "rcfilters-watchlist-markSeen-button": "Alle Änderungen als gesehen markieren",
+       "rcfilters-watchlist-editWatchlist-button": "Deine Liste der beobachteten Seiten bearbeiten",
+       "rcfilters-watchlist-showupdated": "Änderungen an Seiten, die du seit ihrem Auftreten nicht besucht hast, sind <strong>fett</strong> markiert.",
        "rcnotefrom": "Angezeigt {{PLURAL:$5|wird die Änderung|werden die Änderungen}} seit <strong>$3, $4</strong> (max. <strong>$1</strong> Einträge).",
        "rclistfromreset": "Datumsauswahl zurücksetzen",
        "rclistfrom": "Nur Änderungen seit $3, $2 Uhr zeigen.",
index 62bbc75..9a8983b 100644 (file)
        "semiprotectedpagewarning": "'''Σημείωση:''' Αυτή η σελίδα έχει κλειδωθεί ώστε μόνο εγγεγραμμένοι χρήστες μπορούν να την επεξεργαστούν.\nΗ πιο πρόσφατη καταχώρηση στο αρχείο καταγραφής παρέχεται παρακάτω για αναφορά:",
        "cascadeprotectedwarning": "<strong>Προσοχή:</strong> Αυτή η σελίδα έχει κλειδωθεί ώστε μόνο χρήστες με [[Special:ListGroupRights|συγκεκριμένα δικαιώματα]] να μπορούν να την επεξεργαστούν, επειδή περιλαμβάνεται {{PLURAL:$1|στην ακόλουθη|στις ακόλουθες}} διαδοχικά (cascaded) {{PLURAL:$1|προστατευμένη σελίδα|προστατευμένες σελίδες}}:",
        "titleprotectedwarning": "'''Προειδοποίηση: Αυτή η σελίδα έχει κλειδωθεί ώστε χρειάζονται [[Special:ListGroupRights|ειδικά δικαιώματα]] για να δημιουργηθεί.'''\nΗ πιο πρόσφατη καταχώρηση στο αρχείο καταγραφής παρέχεται παρακάτω για αναφορά:",
-       "templatesused": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} στη σελίδα αυτή:",
-       "templatesusedpreview": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} σε αυτήν την προεπισκόπηση:",
+       "templatesused": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} σε αυτή τη σελίδα:",
+       "templatesusedpreview": "{{PLURAL:$1|Πρότυπο που χρησιμοποιείται|Πρότυπα που χρησιμοποιούνται}} σε αυτή την προεπισκόπηση:",
        "templatesusedsection": "{{PLURAL:$1|Πρότυπο|Πρότυπα}} που χρησιμοποιούνται σε αυτή την ενότητα:",
        "template-protected": "(προστατευμένη)",
        "template-semiprotected": "(ημιπροστατευμένη)",
index a44b3cf..2d614f1 100644 (file)
        "rcfilters-empty-filter": "No active filters. All contributions are shown.",
        "rcfilters-filterlist-title": "Filters",
        "rcfilters-filterlist-whatsthis": "How do these work?",
-       "rcfilters-filterlist-feedbacklink": "Provide feedback on the new (beta) filters",
+       "rcfilters-filterlist-feedbacklink": "Tell us what you think about these (new) filtering tools",
        "rcfilters-highlightbutton-title": "Highlight results",
        "rcfilters-highlightmenu-title": "Select a color",
        "rcfilters-highlightmenu-help": "Select a color to highlight this property",
        "rcfilters-liveupdates-button": "Live updates",
        "rcfilters-liveupdates-button-title-on": "Turn off live updates",
        "rcfilters-liveupdates-button-title-off": "Display new changes as they happen",
-       "rcfilters-watchlist-markSeen-button": "Mark all changes as seen",
+       "rcfilters-watchlist-markseen-button": "Mark all changes as seen",
+       "rcfilters-watchlist-edit-watchlist-button": "Edit your list of watched pages",
+       "rcfilters-watchlist-showupdated": "Changes to pages you haven't visited since the changes occurred are in <strong>bold</strong>, with solid markers.",
        "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Reset date selection",
        "rclistfrom": "Show new changes starting from $2, $3",
index e882acd..b4e840e 100644 (file)
        "tog-watchlisthideminor": "Ocultar las ediciones menores de la lista de seguimiento",
        "tog-watchlisthideliu": "Ocultar las ediciones de los usuarios registrados de la lista de seguimiento",
        "tog-watchlistreloadautomatically": "Recargar la lista de seguimiento automáticamente cuando se modifica un filtro (requiere JavaScript)",
+       "tog-watchlistunwatchlinks": "Añadir enlaces directos para seguir o dejar de seguir las entradas de la lista de seguimiento (se requiere JavaScript para utilizar esta funcionalidad)",
        "tog-watchlisthideanons": "Ocultar las ediciones de los usuarios anónimos de la lista de seguimiento",
        "tog-watchlisthidepatrolled": "Ocultar las ediciones verificadas de la lista de seguimiento",
        "tog-watchlisthidecategorization": "Ocultar la categorización de páginas",
        "thisisdeleted": "¿Ver o restaurar $1?",
        "viewdeleted": "¿Quieres ver $1?",
        "restorelink": "{{PLURAL:$1|una edición borrada|$1 ediciones borradas}}",
-       "feedlinks": "Canal:",
+       "feedlinks": "Suministro:",
        "feed-invalid": "El tipo de canal de suscripción no es correcto.",
        "feed-unavailable": "Los canales de sindicación no están disponibles",
-       "site-rss-feed": "Canal RSS de $1",
-       "site-atom-feed": "Canal Atom de $1",
-       "page-rss-feed": "Canal RSS «$1»",
-       "page-atom-feed": "Canal Atom de «$1»",
+       "site-rss-feed": "Suministro RSS de $1",
+       "site-atom-feed": "Suministro Atom de $1",
+       "page-rss-feed": "Suministro RSS de «$1»",
+       "page-atom-feed": "Suministro Atom de «$1»",
        "feed-atom": "Atom",
        "red-link-title": "$1 (la página no existe)",
        "sort-descending": "Orden descendente",
        "permissionserrorstext-withaction": "No tienes permiso para $2, por {{PLURAL:$1|el siguiente motivo|los siguientes motivos}}:",
        "contentmodelediterror": "No puedes editar esta revisión porque su modelo de contenido es <code>$1</code>, que difiere del modelo actual de contenido de la página <code>$2</code>.",
        "recreate-moveddeleted-warn": "<strong>Atención: estás volviendo a crear una página que ha sido borrada anteriormente.</strong>\n\nPiensa si es adecuado continuar editando la página.\nA continuación, se proporciona el registro de borrado y traslados de esta página para más información:",
-       "moveddeleted-notice": "Esta página ha sido borrada.\nA continuación, se proporciona el registro de borrados y traslados de la página para más información.",
-       "moveddeleted-notice-recent": "Esta página se ha eliminado recientemente (durante las últimas 24 horas).\nEl registro de eliminación y traslado de la página se muestran a continuación como referencia.",
+       "moveddeleted-notice": "Esta página ha sido borrada.\nA continuación, se muestra el registro de eliminaciones, protecciones y traslados de la página a modo de referencia.",
+       "moveddeleted-notice-recent": "Esta página se ha eliminado recientemente (durante las últimas 24 horas).\nA continuación, se muestra el registro de eliminaciones, protecciones y traslados de la página a modo de referencia.",
        "log-fulllog": "Ver el registro completo",
        "edit-hook-aborted": "Una extensión ha evitado la edición.\nNo hay explicación disponible.",
        "edit-gone-missing": "No se ha podido actualizar la página.\nParece haber sido borrada.",
        "rcfilters-empty-filter": "No hay filtros activos. Se muestran todas las contribuciones.",
        "rcfilters-filterlist-title": "Filtros",
        "rcfilters-filterlist-whatsthis": "¿Cómo funcionan?",
-       "rcfilters-filterlist-feedbacklink": "Comparte tus comentarios sobre los filtros (beta) nuevos",
+       "rcfilters-filterlist-feedbacklink": "Comparte tus comentarios sobre estas (nuevas) herramientas de filtrado",
        "rcfilters-highlightbutton-title": "Resaltar los resultados",
        "rcfilters-highlightmenu-title": "Selecciona un color",
        "rcfilters-highlightmenu-help": "Selecciona un color para resaltar esta propiedad",
        "rcfilters-filterlist-noresults": "No se encontraron filtros",
        "rcfilters-noresults-conflict": "No se encontraron resultados porque los criterios de búsqueda están en conflicto.",
        "rcfilters-state-message-subset": "Este filtro no tiene ningún efecto debido a que sus resultados se incluyen con los de los siguientes: {{PLURAL:$2|filtro|filtros}} (intente destacarlo para distinguirlo): $1",
-       "rcfilters-state-message-fullcoverage": "Seleccionar todos los filtros de un grupo es lo mismo que seleccionar ninguno, por lo que este filtro no tiene efecto. El grupo incluye: $1",
+       "rcfilters-state-message-fullcoverage": "Seleccionar todos los filtros de este grupo es lo mismo que no seleccionar ninguno, por lo que este filtro no tiene efecto. El grupo incluye $1",
        "rcfilters-filtergroup-authorship": "Autoría de la contribución",
        "rcfilters-filter-editsbyself-label": "Cambios tuyos",
        "rcfilters-filter-editsbyself-description": "Tus propias contribuciones",
        "rcfilters-liveupdates-button-title-on": "Apagar actualizaciones en directo",
        "rcfilters-liveupdates-button-title-off": "Mostar los cambios en tiempo real",
        "rcfilters-watchlist-markSeen-button": "Marcar todos los cambios como vistos",
+       "rcfilters-watchlist-showupdated": "Los cambios hechos a páginas que no has visitado desde que se efectuaron aparecen en <strong>negrita</strong>, acompañados de marcadores sólidos.",
        "rcnotefrom": "Debajo {{PLURAL:$5|aparece el cambio|aparecen los cambios}} desde <strong>$3, $4</strong> (se muestran hasta <strong>$1</strong>).",
        "rclistfromreset": "Restablecer selección de fecha",
        "rclistfrom": "Mostrar cambios nuevos desde las $2 del $3",
        "deadendpages": "Páginas sin salida",
        "deadendpagestext": "Las siguientes páginas no enlazan a otras páginas de {{SITENAME}}.",
        "protectedpages": "Páginas protegidas",
-       "protectedpages-indef": "Sólo protecciones indefinidas",
+       "protectedpages-indef": "Solo protecciones indefinidas",
        "protectedpages-summary": "Esta página enumera las páginas existentes que actualmente están protegidas. Para obtener una lista de títulos que están protegidos desde su creación, véase [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
-       "protectedpages-cascade": "Sólo protecciones en cascada",
+       "protectedpages-cascade": "Solo protecciones en cascada",
        "protectedpages-noredirect": "Ocultar redirecciones",
        "protectedpagesempty": "Actualmente no hay ninguna página protegida con esos parámetros.",
        "protectedpages-timestamp": "Fecha y hora",
        "changecontentmodel-submit": "Cambiar",
        "changecontentmodel-success-title": "Se cambió el modelo de contenido",
        "changecontentmodel-success-text": "Se ha cambiado el tipo de contenido de [[:$1]].",
-       "changecontentmodel-cannot-convert": "El contenido de [[:$1]] no se puede convertir a un tipo de $2.",
+       "changecontentmodel-cannot-convert": "No se puede convertir el contenido de [[:$1]] en un tipo de $2.",
        "changecontentmodel-nodirectediting": "El modelo de contenido $1 no admite la edición directa",
        "changecontentmodel-emptymodels-title": "No hay modelos de contenido disponibles",
        "changecontentmodel-emptymodels-text": "El contenido de [[:$1]] no se puede convertir a ningún tipo.",
        "ipblocklist-otherblocks": "{{PLURAL:$1|Otro bloqueo|Otros bloqueos}}",
        "infiniteblock": "infinito",
        "expiringblock": "expira el $1 a las $2",
-       "anononlyblock": "sólo anón.",
+       "anononlyblock": "solo anón.",
        "noautoblockblock": "bloqueo automático desactivado",
        "createaccountblock": "creación de cuenta bloqueada",
        "emailblock": "correo electrónico bloqueado",
index 9fab7c2..a90570f 100644 (file)
        "changepassword-throttled": "Oled hiljuti proovinud liiga palju kordi sisse logida.\nPalun oota $1, enne kui uuesti proovid.",
        "botpasswords": "Robotiparoolid",
        "botpasswords-summary": "<em>Robotiparoolid</em> võimaldavad API-põhist juurdepääsu kasutajakontole, ilma kasutamata konto peamisi autentimisandmeid. Kui konto on sisse logitud robotiparooliga, võivad saadaolevad kasutajaõigused olla piiratud.\n\nKui sa ei tea, miks sul on vaja robotiparooli, siis on parem seda mitte kasutada. Mitte keegi ei peaks paluma sul robotiparooli genereerida ja seda talle edasi anda.",
+       "botpasswords-disabled": "Robotiparoolid on keelatud.",
+       "botpasswords-no-central-id": "Et robotiparoole kasutada, pead olema ühendkontoga sisse logitud.",
+       "botpasswords-existing": "Olemasolevad robotiparoolid",
        "botpasswords-createnew": "Uue robotiparooli loomine",
-       "botpasswords-label-appid": "Roboti nimi:",
+       "botpasswords-editexisting": "Olemasoleva robotiparooli muutmine",
+       "botpasswords-label-appid": "Robotinimi:",
        "botpasswords-label-create": "Loo",
+       "botpasswords-label-update": "Uuenda",
+       "botpasswords-label-cancel": "Loobu",
+       "botpasswords-label-delete": "Kustuta",
+       "botpasswords-label-resetpassword": "Lähtesta parool",
+       "botpasswords-label-grants": "Rakendatavad volitused:",
+       "botpasswords-help-grants": "Volitused võimaldavad juurdepääsu õigustele, mis su kontol juba on. Siin volituse lubamine ei võimalda juurdepääsu õigustele, mida su kontol muidu pole. Lisateavet leiad [[Special:ListGrants|volituste loetelust]].",
+       "botpasswords-label-grants-column": "Volitatud",
+       "botpasswords-bad-appid": "Robotinimi \"$1\" ei sobi.",
+       "botpasswords-insert-failed": "Ei õnnestunud lisada robotinime \"$1\". Kas see on juba lisatud?",
+       "botpasswords-update-failed": "Ei õnnestunud uuendada robotinime \"$1\". Kas see on kustutatud?",
+       "botpasswords-created-title": "Robotiparool loodud",
+       "botpasswords-created-body": "Robotiparool kasutaja \"$2\" robotinimele \"$1\" on loodud.",
+       "botpasswords-updated-title": "Robotiparool uuendatud",
+       "botpasswords-updated-body": "Robotiparool kasutaja \"$2\" robotinimele \"$1\" on uuendatud.",
+       "botpasswords-deleted-title": "Robotiparool kustutatud",
+       "botpasswords-deleted-body": "Robotiparool kasutaja \"$2\" robotinimele \"$1\" on kustutatud.",
+       "botpasswords-newpassword": "Uus parool, millega kontole <strong>$1</strong> sisse logida, on <strong>$2</strong>. <em>Palun kirjuta see edaspidiseks üles.</em> <br> (Mõni vana robot nõuab, et sisselogimisnimi langeb kokku edaspidise kasutajanimega. Sellisel juhul saad kasutada nime <strong>$3</strong> ja parooli <strong>$4</strong>.)",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider pole saadaval.",
+       "botpasswords-restriction-failed": "Robotiparooli piirangud takistavad sisselogimist.",
+       "botpasswords-invalid-name": "Määratud kasutajanimi ei sisalda robotiparooli eraldajat (\"$1\").",
+       "botpasswords-not-exist": "Kasutaja \"$1\" robotinimele \"$2\" vastav robotiparool puudub.",
        "resetpass_forbidden": "Paroole ei saa muuta",
        "resetpass_forbidden-reason": "Paroole ei saa muuta: $1",
        "resetpass-no-info": "Pead olema sisselogitud, et sellele lehele pääseda.",
        "passwordreset-emailelement": "Kasutajanimi: \n$1\n\nAjutine parool: \n$2",
        "passwordreset-emailsentemail": "Kui oled sidunud konto selle e-posti aadressiga, siis saadetakse sulle parooli lähtestamise e-kiri.",
        "passwordreset-emailsentusername": "Parooli lähtestamise e-kiri saadetakse, kui olemas on kontoga seotud e-posti aadress.",
+       "passwordreset-nocaller": "Kutse saatja peab olema ära toodud",
+       "passwordreset-nosuchcaller": "Kutse saatjat pole olemas: $1",
        "passwordreset-ignored": "Parooli lähtestamine jäi rahuldamata. Võimalik, et ühtegi pakkujat polnud häälestatud.",
        "passwordreset-invalidemail": "Vigane e-posti aadress",
        "passwordreset-nodata": "Ära toomata jäid nii kasutajanimi kui ka e-posti aadress",
        "recentchanges-legend-heading": "<strong>Seletus:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (vaata ka [[Special:NewPages|uute lehekülgede loendit]])",
        "recentchanges-submit": "Näita",
+       "rcfilters-tag-remove": "Eemalda \"$1\"",
        "rcfilters-legend-heading": "<strong>Lühendite loetelu:</strong>",
        "rcfilters-other-review-tools": "<strong>Muud ülevaatusriistad</strong>",
        "rcfilters-group-results-by-page": "Rühmita tulemused lehekülje kaupa",
        "rcfilters-hours-title": "Viimased tunnid",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|päev|päeva}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|tund|tundi}}",
+       "rcfilters-highlighted-filters-list": "Esile tõstetud: $1",
        "rcfilters-quickfilters": "Salvestatud filtrid",
        "rcfilters-quickfilters-placeholder-title": "Linke pole veel salvestatud",
        "rcfilters-quickfilters-placeholder-description": "Et filtri sätted salvestada ja et neid hiljem uuesti kasutada, klõpsa alloleva aktiivsete filtrite loendi juures järjehoidjaikooni.",
        "rcfilters-filter-watchlist-watchednew-description": "Muudatused jälgimisloendi lehekülgedel, mida sa pole pärast muudatuse tegemist külastanud.",
        "rcfilters-filter-watchlist-notwatched-label": "Pole jälgimisloendis",
        "rcfilters-filter-watchlist-notwatched-description": "Kõik muu peale sinu jälgimisloendi lehekülgedel tehtud muudatuste.",
+       "rcfilters-filtergroup-watchlistactivity": "Jälgimisloendi tegevused",
+       "rcfilters-filter-watchlistactivity-unseen-label": "Nägemata muudatused",
+       "rcfilters-filter-watchlistactivity-unseen-description": "Muudatused lehekülgedel, mida sa pole pärast muudatuste tegemist külastanud.",
+       "rcfilters-filter-watchlistactivity-seen-label": "Nähtud muudatused",
+       "rcfilters-filter-watchlistactivity-seen-description": "Muudatused lehekülgedel, mida oled pärast muudatuste tegemist külastanud.",
        "rcfilters-filtergroup-changetype": "Muudatuse tüüp",
        "rcfilters-filter-pageedits-label": "Lehekülgede muutmine",
        "rcfilters-filter-pageedits-description": "Näiteks viki sisu, arutelude ja kategooriate kirjelduste muutmine.",
        "rcfilters-liveupdates-button": "Uuendused reaalajas",
        "rcfilters-liveupdates-button-title-on": "Lülita reaalajas uuendamine välja",
        "rcfilters-liveupdates-button-title-off": "Näita uusi muudatusi kohe nende tegemise järel",
+       "rcfilters-watchlist-markSeen-button": "Märgi kõik muudatused nähtuks",
        "rcnotefrom": "Allpool on toodud {{PLURAL:$5|muudatus|muudatused}} alates: <strong>$3, kell $4</strong> (näidatakse kuni <strong>$1</strong> muudatust)",
        "rclistfromreset": "Lähtesta kuupäeva valik",
        "rclistfrom": "Näita muudatusi alates: $3, kell $2",
index 667470d..d93d92f 100644 (file)
        "editundo": "desegin",
        "diff-empty": "(Ez dago alderik)",
        "diff-multi-sameuser": "(Erabiltzaile berdinaren {{PLURAL:$1|erdiko ekarpen bat ez da|$1 erdiko ekarpen ez dira}} erakusten)",
-       "diff-multi-otherusers": "(Erabiltzaile berdinaren {{PLURAL:$1|erdiko ekarpen bat ez da|$1 erdiko ekarpen ez dira}} erakusten)",
+       "diff-multi-otherusers": "({{PLURAL:$1|Tarteko berrikusketa bat|$1 tarteko berrikusketak}}  {{PLURAL:$2|beste erabiltzaile bat|$2 erabiltzaileak}} egina ez da erakusten)",
        "searchresults": "Bilaketaren emaitzak",
        "searchresults-title": "«$1» bilaketaren  emaitzak",
        "titlematches": "Emaitzak artikuluen izenburuetan",
index 06fe26f..d43ab0e 100644 (file)
        "unprotectedarticle": "صفحهٔ «[[$1]]» را از محافظت بیرون آورد",
        "movedarticleprotection": "تنظیمات محافظت را از «[[$2]]» به «[[$1]]» منتقل کرد",
        "protectedarticle-comment": "«[[$1]]» را {{GENDER:$2|محافظت کرد}}",
-       "modifiedarticleprotection-comment": "سطح محاظفت «[[$1]]» را {{GENDER:$2|تغییر داد}}",
+       "modifiedarticleprotection-comment": "سطح محافظت «[[$1]]» را {{GENDER:$2|تغییر داد}}",
        "unprotectedarticle-comment": "«[[$1]]» را از محافظت {{GENDER:$2|در آورد}}",
        "protect-title": "تغییر وضعیت محافظت «$1»",
        "protect-title-notallowed": "مشاهدهٔ سطح محافظت «$1»",
index 87183fa..3dcb491 100644 (file)
        "editingsection": "Muokataan osiota sivusta $1",
        "editingcomment": "Muokataan uutta osiota sivulla $1",
        "editconflict": "Päällekkäinen muokkaus: $1",
-       "explainconflict": "Joku muu on muuttanut tätä sivua sen jälkeen, kun aloit muokata sitä.\nYlempi tekstialue sisältää tämänhetkisen tekstin.\nTekemäsi muutokset näkyvät alemmassa ikkunassa.\nSinun täytyy yhdistää muutoksesi olemassa olevaan tekstiin.\n'''Vain''' ylemmässä alueessa oleva teksti tallentuu, kun tallennat sivun.",
+       "explainconflict": "Joku muu on muuttanut tätä sivua sen jälkeen, kun aloit muokata sitä.\nYlempi tekstialue sisältää tämänhetkisen tekstin.\nTekemäsi muutokset näkyvät alemmassa ikkunassa.\nSinun täytyy yhdistää muutoksesi olemassa olevaan tekstiin.\n<strong>Vain</strong> ylemmässä alueessa oleva teksti tallentuu, kun napsautat \"$1\".",
        "yourtext": "Oma tekstisi",
        "storedversion": "Tallennettu versio",
        "nonunicodebrowser": "'''Selaimesi ei ole Unicode-yhteensopiva. Ole hyvä ja vaihda selainta, ennen kuin muokkaat sivua.'''",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (katso myös [[Special:NewPages|lista uusista sivuista]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Näytä",
+       "rcfilters-tag-remove": "Poista '$1'",
        "rcfilters-legend-heading": "<strong>Luettelo lyhenteistä:</strong>",
        "rcfilters-other-review-tools": "<strong>Muut arviointityökalut</strong>",
        "rcfilters-group-results-by-page": "Ryhmitä tulokset sivujen mukaan",
        "rcfilters-grouping-title": "Ryhmitys",
        "rcfilters-activefilters": "Aktiiviset suodattimet",
+       "rcfilters-advancedfilters": "Kehittyneet suodattimet",
        "rcfilters-limit-title": "Näytettävät muutokset",
        "rcfilters-limit-shownum": "Näytä {{PLURAL:$1|viimeisin muutos|$1 viimeisintä muutosta}}",
        "rcfilters-days-title": "Viimeisimmät päivät",
        "rcfilters-hours-title": "Viimeisimmät tunnit",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|päivä|päivää}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|tunti|tuntia}}",
+       "rcfilters-highlighted-filters-list": "Korostettu: $1",
        "rcfilters-quickfilters": "Tallennetut suodattimet",
        "rcfilters-quickfilters-placeholder-title": "Ei vielä tallennettuja linkkejä",
        "rcfilters-savedqueries-defaultlabel": "Tallennetut suodattimet",
        "rcfilters-savedqueries-unsetdefault": "Poista oletuksena",
        "rcfilters-savedqueries-remove": "Poista",
        "rcfilters-savedqueries-new-name-label": "Nimi",
+       "rcfilters-savedqueries-new-name-placeholder": "Kuvaa suodattimen tarkoitusta",
        "rcfilters-savedqueries-apply-label": "Luo suodatin",
        "rcfilters-savedqueries-apply-and-setdefault-label": "Luo oletussuodatin",
        "rcfilters-savedqueries-cancel-label": "Peru",
        "rcfilters-filterlist-noresults": "Ei löytynyt suodattimia",
        "rcfilters-noresults-conflict": "Tuloksia ei löytynyt, koska hakuehdot ovat ristiriidassa",
        "rcfilters-state-message-subset": "Tällä suodattimella ei ole vaikutusta, koska sen tulokset sisältyvät {{PLURAL:$2|seuraavaan laajempaan suodattimeen|seuraaviin laajempiin suodattimiin}} (kokeile korostusta sen erottamiseksi): $1",
-       "rcfilters-state-message-fullcoverage": "Ryhmän kaikkien suodattimien valitseminen on sama, kuin ei valitse mitään, joten tällä suodattimella ei ole vaikutusta. Ryhmään sisältyy: $1",
+       "rcfilters-state-message-fullcoverage": "Tässä ryhmässä kaikkien suodattimien valitseminen on sama, kuin ei valitse mitään, joten tällä suodattimella ei ole vaikutusta. Ryhmään sisältyy: $1",
        "rcfilters-filtergroup-authorship": "Muutoksen tekijä",
-       "rcfilters-filter-editsbyself-label": "Muutoksesi",
+       "rcfilters-filter-editsbyself-label": "Sinun tekemät muutokset",
        "rcfilters-filter-editsbyself-description": "Tekemäsi muutokset.",
        "rcfilters-filter-editsbyother-label": "Muiden muutokset",
        "rcfilters-filter-editsbyother-description": "Muiden käyttäjien tekemät muutokset.",
        "rcfilters-filter-watchlist-watched-label": "Tarkkailulistalla",
        "rcfilters-filter-watchlist-watched-description": "Muutokset tarkkailulistalla oleviin sivuihin.",
        "rcfilters-filter-watchlist-watchednew-label": "Uudet tarkkailulistan muutokset",
+       "rcfilters-filter-watchlist-watchednew-description": "Muutokset tarkkailulistalla oleviin sivuihin, joilla et ole vieraillut sen jälkeen, kun sivuille on tehty muutoksia.",
        "rcfilters-filter-watchlist-notwatched-label": "Ei tarkkailulistalla",
        "rcfilters-filtergroup-changetype": "Muutoksen tyyppi",
        "rcfilters-filter-pageedits-label": "Sivun muokkaukset",
        "rcfilters-typeofchange-conflicts-hideminor": "\"Muutoksen tyyppi\" on ristiriidassa \"Pienet muutokset\" -suodattimen kanssa. Joitain muutostyyppejä ei voida merkitä \"pieniksi\".",
        "rcfilters-filtergroup-lastRevision": "Viimeisimmät versiot",
        "rcfilters-filter-lastrevision-label": "Viimeisin versio",
-       "rcfilters-filter-lastrevision-description": "Viimeisin muutos sivulle.",
-       "rcfilters-filter-previousrevision-label": "Aikaisemman versiot",
+       "rcfilters-filter-lastrevision-description": "Vain viimeisin muutos sivulle.",
+       "rcfilters-filter-previousrevision-label": "Ei viimeisin muutos",
+       "rcfilters-view-advanced-filters-label": "Kehittyneet suodattimet",
        "rcfilters-view-namespaces-tooltip": "Suodata tuloksia nimiavaruuden mukaan",
        "rcnotefrom": "Alla ovat muutokset <strong>$3, $4</strong> lähtien. (Enintään <strong>$1</strong> näytetään.)",
+       "rclistfromreset": "Tyhjennä ajankohdan valinta",
        "rclistfrom": "Näytä uudet muutokset $3 kello $2 alkaen",
        "rcshowhideminor": "$1 pienet muutokset",
        "rcshowhideminor-show": "Näytä",
        "newimages-user": "IP-osoite tai käyttäjänimi:",
        "newimages-showbots": "Näytä bottien tekemät tallennukset",
        "newimages-hidepatrolled": "Piilota tarkastetut tiedostotallennukset",
+       "newimages-mediatype": "Median tyyppi:",
        "noimages": "Ei uusia tiedostoja.",
        "gallery-slideshow-toggle": "Vaihda pienoiskuvaa",
        "ilsubmit": "Hae",
index 0745ac7..bfce7d9 100644 (file)
        "missing-article": "La base de données n’a pas trouvé le texte d’une page qu’elle aurait dû trouver, intitulée « $1 » $2.\n\nGénéralement, cela survient en suivant un lien vers un diff périmé ou vers l’historique d’une page supprimée.\n\nSi ce n’est pas le cas, il peut s’agir d’une anomalie dans le programme.\nVeuillez la signaler à un [[Special:ListUsers/sysop|administrateur]] sans oublier de lui indiquer l’URL de la page.",
        "missingarticle-rev": "(numéro de version : $1)",
        "missingarticle-diff": "(diff : $1, $2)",
-       "readonly_lag": "La base de données a été automatiquement verrouillée pendant que les serveurs secondaires se réalignent sur le serveur principal.",
+       "readonly_lag": "La base de données a été automatiquement verrouillée pendant que les serveurs secondaires se réalignent sur le serveur principal",
        "nonwrite-api-promise-error": "L’entête HTTP « <code>Promise-Non-Write-API-Action:</code> » a été envoyé mais la requête a été faite à un module d’écriture de l’API.",
        "internalerror": "Erreur interne",
        "internalerror_info": "Erreur interne : $1",
        "emailnotauthenticated": "Votre adresse de courriel n’est pas encore confirmée.\nAucun courriel ne sera envoyé pour chacune des fonctions suivantes.",
        "noemailprefs": "Indiquez une adresse de courriel dans vos préférences pour utiliser ces fonctions.",
        "emailconfirmlink": "Confirmez votre adresse de courriel",
-       "invalidemailaddress": "Cette adresse courriel ne peut pas être acceptée car son format paraît incorrect.\nEntrez une adresse bien formatée ou laissez ce champ vide.",
+       "invalidemailaddress": "Cette adresse courriel ne peut pas être acceptée car son format paraît incorrect.\nEntrez une adresse correctement formatée ou laissez ce champ vide.",
        "cannotchangeemail": "Les adresses de courriel des comptes ne peuvent pas être modifiées sur ce wiki.",
        "emaildisabled": "Ce site ne peut pas envoyer de courriels.",
        "accountcreated": "Compte créé",
        "botpasswords-updated-title": "Mot de passe de robots mis à jour",
        "botpasswords-updated-body": "Le mot de passe pour le robot « $1 » de l'utilisateur « $2 » a été mis à jour.",
        "botpasswords-deleted-title": "Mot de passe de robots supprimé",
-       "botpasswords-deleted-body": "Le mot de passe pour le robot « $1 » de l'utilisateur « $2 » a été supprimé.",
+       "botpasswords-deleted-body": "Le mot de passe pour le robot « $1 » de l'{{GENDER:$2|utilisateur|utilisatrice}} « $2 » a été supprimé.",
        "botpasswords-newpassword": "Le nouveau mot de passe pour se connecter à <strong>$1</strong> est <strong>$2</strong>. <em>Veuillez l’enregistrer pour y faire référence ultérieurement.</em><br> (Pour les anciens robots qui nécessitent que le nom fourni à la connexion soit le même que le nom d'utilisateur éventuel, vous pouvez aussi utiliser  <strong>$3</strong> comme nom d'utilisateur et <strong>$4</strong> comme mot de passe).",
        "botpasswords-no-provider": "BotPasswordsSessionProvider n’est pas disponible.",
        "botpasswords-restriction-failed": "Les restrictions de mot de passe de robots empêchent cette connexion.",
        "botpasswords-invalid-name": "Le nom d’utilisateur spécifié ne contient pas de séparateur de mot de passe de robots (« $1 »).",
-       "botpasswords-not-exist": "L’utilisateur « $1 » n’a pas le mot de passe de robots intitulé « $2 ».",
+       "botpasswords-not-exist": "L’{{GENDER:$1|utilisateur|utilisatrice}} « $1 » n’a pas de mot de passe de robots intitulé « $2 ».",
        "resetpass_forbidden": "Les mots de passe ne peuvent pas être changés",
        "resetpass_forbidden-reason": "Les mots de passe ne peuvent pas être modifiés : $1",
        "resetpass-no-info": "Vous devez être connecté pour avoir accès directement à cette page.",
        "rcfilters-empty-filter": "Aucun filtre actif. Toutes les contributions sont affichées.",
        "rcfilters-filterlist-title": "Filtres",
        "rcfilters-filterlist-whatsthis": "Comment ça marche ?",
-       "rcfilters-filterlist-feedbacklink": "Fournir un commentaire sur les nouveaux filtres (en bêta)",
+       "rcfilters-filterlist-feedbacklink": "Nous dire ce que vous pensez de ces (nouveaux) outils de filtrage",
        "rcfilters-highlightbutton-title": "Mettre en valeur les résultats",
        "rcfilters-highlightmenu-title": "Choisir une couleur",
        "rcfilters-highlightmenu-help": "Sélectionner une couleur pour mettre en évidence cette propriété",
        "rcfilters-liveupdates-button-title-on": "Désactiver les mises à jour à chaud",
        "rcfilters-liveupdates-button-title-off": "Afficher les nouveaux changements dès qu'ils se produisent",
        "rcfilters-watchlist-markSeen-button": "Marquer toutes les modifications comme vues",
+       "rcfilters-watchlist-editWatchlist-button": "Modifier votre liste de pages suivies",
+       "rcfilters-watchlist-showupdated": "Les modifications faites aux pages que vous n’avez pas visitées depuis qu’elles ont été modifiées sont en <strong>gras</strong>, avec des balises unies.",
        "rcnotefrom": "Ci-dessous {{PLURAL:$5|la modification effectuée|les modifications effectuées}} depuis le <strong>$3, $4</strong> (affichées jusqu’à <strong>$1</strong>).",
        "rclistfromreset": "Réinitialiser la sélection de la date",
        "rclistfrom": "Afficher les nouvelles modifications depuis le $3 à $2",
index b1c3880..8390907 100644 (file)
        "minoredit": "Utiye biloli'o ngo'idi",
        "watchthis": "Dahayi halaman botiye",
        "savearticle": "Tahuwa halaman",
+       "preview": "Bilohipo",
        "showpreview": "Bilohi pratayang",
        "showdiff": "Popobilohe u loboli'a",
        "anoneditwarning": "<strong>Mopo'eela:</strong> Yi'o diipo tilumuwo. Alamat IP olemu ma ontonga lo tawu daata wonu yi'o momoli'o. Wonu Yi'o <strong>[$1 tumuwoto log]</strong> meyalo <strong>[$2 mohutu akun]</strong>, u biloli'umu madiatribusikan ode tanggulumu, wolo huna uweewoliyo.",
+       "blockedtext": "'''Tanggulumu meyalo alamat IP ulemu ma diblokir.'''\n\nBlokir pilohutu lo $1.\nAlasani u yilohiliyo de'uwito ''$2''.\n\n* Blokir tilumula lonto: $8\n* Blokir mopulita to: $6\n* Sasaran pemblokiran: $7\n\nYi'o mowali mohubungi $1 meyalo [[{{MediaWiki:Grouppage-sysop}}|pengurus uweewo]] motombilu lo masalah botiye.\n\nYi'o ja mowali mopohuna fitur ''molawo surel ode pengguna botiye'' ngopohiya yi'o ma lopotuwoto alamat surel u sah to[[Special:Preferences|preferensi akun]] wawu yi'o didu diblokir mopomake.\n\nAlamat IP ulemu de'uwito $3, wawu ID pemblokiran de'uwito $5.\nWuduwa mayi tala tuwawu meyalo oluwo lo habari botiye to timi'idu yiyintu monto olemu.",
        "loginreqlink": "tumuwoto log",
        "newarticletext": "Yi'o lodudu'a wumbuta ode halaman diya'a. \nWonu mohutu halaman botiye, ketik tuwango halaman to kotak to tibawa botiye (bilohi [$1 halaman wubodu] ode habari wumbutiyo). \nWonu Yi'o ja sangaja tilumuwota ode halaman botiye, kutiya tombol <strong>mohuwalingo</strong>.",
+       "anontalkpagetext": "---\n<em>Utiya halaman lo'iya ode pengguna anonim ta diipo lohutu akun, meyalo tita ta ja lopohuna.<em>\nSababu uwito, ami lopohuna alamat IP u pilomakeliyo molapu oliyo.\nAlamat IP odito mowali pomake lotawu ngolo lota pe'eenta.\nWonu yi'o pengguna anonim wawu lorasa lo'iya lotawu ja mopiyohu ode olemu, toduwolo [[Special:CreateAccount|create a account]] meyalo [[Special:UserLogin|log in]] alihu mopelehiya u molilingu wolo pengguna anonim to sa'ati tuwawu.",
        "noarticletext": "Sa'ati botiye diya'a teks to halaman botiye.\nYi'o mowali [[Special:Search/{{PAGENAME}}|mololohu  judul halaman botiye]] to halaman-halaman uweewo, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mololohu log a'ayita], meyalo [{{fullurl:{{FULLPAGENAME}}|action=edit}} mohutu halaman botiye]</span>.",
        "noarticletext-nopermission": "!Sa'ati botiye diya'a teks to halaman boptiye.\nYi'o mowali [[Special:Search/{{PAGENAME}}|mololohu judul halaman botiye]] to halaman-halaman uweewo, meyalo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mololohu log a'ayita]</span>, dabo Yi'o ja o ijin mohutu halaman botiye.",
        "userpage-userdoesnotexist-view": "Ta ohu'uwo \"$1\" diyaalu to daputari.",
+       "clearyourcache": "<strong>Tuladu:</strong>Yilapato tilahu, yi'o musti lumawode to cache browser web bolo mo'oonto u yiloboli'a.\n* <strong>Firefox/Safari:</strong> Tahan <em>Shift</em> to'u yiloduto <em>Reload</em>, meyalo woduta <em>Ctrl-F5</em> meyalo <em>Ctrl-R</em> (<em>⌘-R</em> di Mac)\n* <strong>Google Chrome:</strong> Woduta <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> di Mac)\n* <strong>Internet Explorer:</strong> Woduta <em>Ctrl</em> to'u yiloduto <em>Refresh</em>, meyalo woduta <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Pergi ke <em>Menu → Settings </em> (<em>Opera → Preferences</em> di Mac) lalu ke <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
+       "previewnote": "<Strong>Elayi utiye bo mopobilohu.<strong>\nU biloli'umu diipo tilahu!",
+       "continue-editing": "Ntali ode area momoli'o",
        "editing": "Momoli'o $1",
        "creating": "Mohutu $1",
        "editingsection": "Momoli'o $1 (tayadu)",
        "yourdiff": "Hihede",
        "templatesused": "{{PLURAL:$1|Template}} pilopohuna to halaman botiye:",
+       "templatesusedpreview": "{{PLURAL:$1|Template|Templates}} pilomake to'u mopobilohu.",
        "template-protected": "(he dahalo)",
        "template-semiprotected": "(dahalo-ngowa)",
        "hiddencategories": "Halaman botiye woluwo anggota {{PLURAL:$1|1 kategori wanto-wanto'o $1}}:",
+       "permissionserrors": "Tilala haku momu'o",
        "permissionserrorstext-withaction": "Yi'o ja haku akses $2, sababu {{PLURAL:$1|alasani}} botiya:",
-       "moveddeleted-notice": "Halaman botiye ma yiluluto.\nSebagai referensi, botiya log piloluluta wawu piloheyiya halaman botiye.",
+       "recreate-moveddeleted-warn": "<Strong>Mopo'ota: Yi'o lohutu ulangi hlaman u ma yiluluto.<strong>\n\nPopotimbangiyapo huhutumu botiye delo mowali poturusiyolo.\nBotiya log piloluluta wawu piloheyiya halaman botiye.",
+       "moveddeleted-notice": "Halaman botiye ma yiluluto.\nLog piloluluta, pilodahawa wawu piloheyiya halaman botiye woluwo to tibawa pohutu referensi.",
        "postedit-confirmation-saved": "Biloli'umu ma tilahu.",
        "edit-already-exists": "Ja mowali mohutu halaman bohu. Ma woluwo.",
        "content-model-wikitext": "tuladu wiki",
+       "undo-failure": "U biloli'a botiya ja mowali pohuwalingo sababu lodulehe ta lomoli'o.",
        "viewpagelogs": "Bilohi log lo halaman botiye",
        "currentrev-asof": "Biloli'o pulitiyo to $1",
        "revisionasof": "Biloli'o to $1",
        "currentrevisionlink": "Biloli'o pulitiyo",
        "cur": "mst",
        "last": "diipo",
+       "histlegend": "Tulawota diff: Tuwoti kasi lo radio loboli'a u mopobandingiyo wawu woduta enter meyalo tombol to tibawa.<br />\nLegenda: <strong>({{int:cur}})</strong> = hihede wolo biloli'a pulitiyo, <strong>({{int:last}})</strong> = hihede wolo u biloli'a muloolo, <strong>{{int:minoreditletter}}</strong> = bilili'o ngo'idi.",
        "history-fieldset-title": "Lolohe u biloli'o",
        "histfirst": "mohihewo da'a",
        "histlast": "bohu da'a",
        "history-feed-description": "Riwayati bilolio to halaman wiki botiye",
        "history-feed-item-nocomment": "$1 to $2",
        "rev-delundel": "popobilohe/wanto'a",
+       "mergelog": "Log mopohimbunguwo",
        "history-title": "Riwayati lo'u loboli'a lonto \"$1\"",
        "difference-title": "$1 hihede revisi",
        "lineno": "Baarisi $1:",
        "compareselectedversions": "Popotadenga u tilulawoto",
        "editundo": "pohuwalinga",
+       "diff-empty": "(Diya'a hihedeliyo)",
        "diff-multi-sameuser": "({{PLURAL:$1|$1 revisi wolota}} pilohutu lo tawu ngota ja pilopobilohu)",
+       "diff-multi-otherusers": "({{PLURAL:$1|Tuwawu lopo'opiyohu wolota|$1 lopo'opiyohu wolota}} pilohutu {{PLURAL:$2|ngota ta ohu'uwo uweewo|$2 ta ohu'uwo}} ja pilopobilohu)",
        "searchresults": "U yilotapu",
        "searchresults-title": "U yilotapu lololohe \"$1\"",
        "prevn": "{{PLURAL:$1|$1}} to'udiipo",
        "nextn": "{{PLURAL:$1|$1}} lapatiyoma'o",
+       "prevn-title": "To'u diipo $1 {{PLURAL:$1|hasili}}",
        "nextn-title": "$1 {{PLURAL:$1|hasili}}lapatiyoma'o",
        "shown-title": "Popobilohe $1 {{PLURAL:$1|haasili}} per halaman",
        "viewprevnext": "Bilohi ($1 {{int:pipe-separator}} $2) ($3)",
+       "searchmenu-exists": "<strong>Woluwo halaman otanggula \"[[:$1]]\" to wiki botiye. </ strong> {{PLURAL: $2|0=|Bilohi olo u yilotapu uweewo.}}",
        "searchmenu-new": "<strong>mohutu halaman \"[[:$1]]\" to wiki botiya! {{PLURAL:$2|0=Bilohi halaman u yilotapu yilolohumu.|Bilohi hasili u yilotapu to'u yilolohu}}",
        "searchprofile-articles": "Tuwango halaman",
        "searchprofile-images": "Multimedia",
        "searchprofile-everything-tooltip": "Mololohe nga'amila tuwango situs (wolo halaman polo'iyalo)",
        "searchprofile-advanced-tooltip": "Mololohu to huwali lo tanggulo biasa",
        "search-result-size": "$1 ({{PLURAL:$2|1 tahe|$2 tahe}})",
+       "search-result-category-size": "{{PLURAL:$1|1 anggota|$1 anggota}} ({{PLURAL:$2|1 subkategori|$2 subkategori}}, {{PLURAL:$3|1 berkas|$3 berkas}})",
        "search-redirect": "(pilobale lonto $1)",
        "search-section": "(tayadu) $1",
        "search-file-match": "(sama lo tuwango berkas)",
        "default": "kakali",
        "yourrealname": "Tanggula banari",
        "yourlanguage": "Bahasa",
+       "group-bot": "Bot",
+       "group-sysop": "Pengurus",
        "grouppage-bot": "{{ns:project}}:Bot",
+       "grouppage-sysop": "{{ns:project}}:Pengurus",
        "right-writeapi": "Mopohuna API moluladu",
        "newuserlogpage": "Log ta ohu'uwo bohu",
+       "rightslog": "Log haku ta ohu'uwo",
        "action-edit": "boli'a halaman botiye",
+       "action-createaccount": "mohutu akun lo ta ohu'uwo botiya",
        "enhancedrc-history": "riwayati",
        "recentchanges": "Boheli loboli'a mola",
        "recentchanges-legend": "Tulawotolo boheli loboli'a mola",
        "recentchanges-label-plusminus": "Loboli'o tu'udu halaman boti to delomo bita",
        "recentchanges-legend-heading": "<strong>Keterangan:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (bilohi olo [[Special:NewPages|list of new pages]])",
+       "rcnotefrom": "To tibawa botiye {{PLURAL:$5|loboli'a}} anggadu <strong>$3, $4</strong> (popobilohe sambe <strong>$1</strong> loboli'a).",
        "rclistfrom": "Popobilohe u loboli'a lonto $2, $3",
        "rcshowhideminor": "$1 biloli'o ngo'idi",
        "rcshowhideminor-show": "Popobilohe",
        "rcshowhideanons": "$1 biloli'o lo tawu weewo",
        "rcshowhideanons-show": "Popobilohe",
        "rcshowhideanons-hide": "Wanto'a",
+       "rcshowhidepatr": "$1 biloli'o terpatroli",
        "rcshowhidemine": "$1 biloli'u'u",
        "rcshowhidemine-show": "Popobilohe",
        "rcshowhidemine-hide": "Wanto'a",
        "rc-change-size-new": "$1 {{PLURAL:$1|bita}} lapato biloli'o",
        "rc-old-title": "bohuliyo pilohutu odelo \"$1\"",
        "recentchangeslinked": "Loboli'a wayitiyo",
+       "recentchangeslinked-feed": "Loboli'a wayitiyo",
        "recentchangeslinked-toolbox": "Loboli'o wayitiyo",
        "recentchangeslinked-title": "Loboli'a a'aayita wolo $1",
        "recentchangeslinked-summary": "Utiye daputari lo'ubawa to halaman a'ayita wolo halaman tuwawu (meyalo tayadu to kategori tuwawu)\nHalaman to [[Special:Watchlist|he'awasiyamu]] ontonga <strong>cetakiya mohulodu</strong>.",
        "license": "Lisensi",
        "license-header": "Tayadu lisensi",
        "imgfile": "berkas",
+       "listfiles": "Daputari berkas",
        "file-anchor-link": "Berkas",
        "filehist": "Riwaayati lo berkas",
        "filehist-help": "Klik to tanggal/wakutu momilohe berkas to saa'ati botiye.",
        "filehist-datetime": "Tanggal/Wakutu",
        "filehist-thumb": "Kiki'o",
        "filehist-thumbtext": "u kiki'o versi lo $1",
+       "filehist-nothumb": "Diya'a gambari kikino",
        "filehist-user": "Ta ohu'uwo",
        "filehist-dimensions": "Dimensi",
        "filehist-comment": "Lo'iya",
        "imagelinks": "Berkas u pilopohuna",
        "linkstoimage": "{{PLURAL:$1|halaman lapatiyoma'o}} o wumbuta ode berkas botiye:",
+       "linkstoimage-more": "Limbata lo $1 {{PLURAL:$1|halaman}} o wumbuta ode berkas botiye.\nDaputari botiya mopobilohu {{PLURAL:$1|wumbuta halaman bungaliyo}} ode berkas botiye wamba'o.\nWoluwo [[Special:WhatLinksHere/$2|daputari ganapu]] u sadi-sadiya.",
        "nolinkstoimage": "Diya'a halaman u owumbuta ode berkas botiye",
+       "linkstoimage-redirect": "$1 (mopobale berkas) $2",
        "sharedupload-desc-here": "Berkas botiye lonto $1 wawu hepohunaliyo to poroyek uweewo.\nDeskripsi lonto [$2 halaman deskripsiliyo] woluwo to tibawa botiya.",
        "filepage-nofile": "Diya'a berkas lo tanggula botiye",
        "upload-disallowed-here": "Yi'o diila mowali modeehe berkas botiye",
        "randompage": "Halaman totonula",
        "statistics": "Statistik",
+       "double-redirect-fixer": "Revisi mopobale",
        "nbytes": "$1 {{PLURAL:$1|bita}}",
        "nmembers": "$1 {{PLURAL:$1|tuwango}}",
+       "prefixindex": "Nga'amila halaman woluwo awalan",
        "listusers": "Daputari ta ohu'uwo",
        "newpages": "Halaman bohu",
        "move": "Heyiya",
        "booksources": "Bungo buku",
        "booksources-search-legend": "Lolohe to bungo lo buku",
        "booksources-search": "Lolohe",
+       "specialloguserlabel": "Ta ohu'uwo",
+       "speciallogtitlelabel": "Target (judul meyalo{{ns:ta ohu'uwo}}:tanggulo ta ohu'uwo ode ta ohu'uwo)",
        "log": "Log",
        "all-logs-page": "Nga'amila log publik",
+       "alllogstext": "Himbunguwa nga'amila log u sadi-sadiya to {{SITENAME}}.\nYi'o mowali mopo'okikingo bibilohu lo'u molulawota tayadu log, tanggulo ta ohu'uwo (sensitif kapitalisasi), meyalo judul halaman (sensitif kapitalisasi olo).",
+       "logempty": "Ja yilotapu entri log u mohumayawa.",
        "allpages": "Nga'amila halaman",
        "allarticles": "Nga'amila halaman",
        "allpagessubmit": "Ntali",
+       "allpages-hide-redirects": "Wanto'a mopobale",
        "categories": "Kategori",
+       "listgrouprights-members": "(daputari lo anggota)",
+       "emailuser": "Lawola surel ta ohu'uwo botiye",
        "usermessage-editor": "Sistem lo tahuli",
        "watchlist": "U he'awasiyalo",
        "mywatchlist": "Daputari he'awasiyalo",
+       "watchlistfor2": "Ode $1 $2",
        "watch": "Dahayi",
        "unwatch": "Batali mongawasi",
+       "watchlist-details": "{{PLURAL:$1|$1 halaman}} to dputari he'awasiyamu, ja wayitiyo halaman lo'iya.",
+       "wlheader-showupdated": "Halaman ma loboli'a to pulitiyo nila'omu mowali bilohela to <strong>bold</strong>.",
+       "wlnote": "To tibawa botiye {{PLURAL:$1|loboli'a|<strong>$1</strong> loboli'a}} pulitiyo to delomo {{PLURAL:$2|jam|<strong>$2</strong> jam}}, per $3, $4.",
        "wlshowlast": "Popobilohe $1 jam $2 dulahe pulitiyo",
+       "watchlist-options": "Tulawoto daputari he'awasiyalo",
+       "enotif_reset": "Tuwoti nga'amila halaman ma nila'o",
        "dellogpage": "Log loluluto",
        "rollbacklink": "wuwalinga",
        "rollbacklinkcount": "pohuwalinga $1 {{PLURAL:$1|biloli'o}}",
        "protectlogpage": "Log mopo'aamani",
        "protectedarticle": "modaha \"[[$1]]\"",
+       "modifiedarticleprotection": "momoli'a lenggota lo dudaha ode \"[[$1]]\"",
        "protect-default": "Poluliya nga'amila ta ohu'uwo",
        "restriction-edit": "Boli'a",
        "restriction-move": "Heyiya",
        "tooltip-namespace_association": "Centang halaman botiye u mopowayito huwali lo tanggulo lo'iyawa meyalo subjek u a'ayita wolo huwali lo tanggulo u tilulawoto.",
        "blanknamespace": "(Bungaliyo)",
        "contributions": "Kontribusi {{GENDER:$1|Ta ohu'uwo}}",
+       "contributions-title": "Kontribusi ta ohu'uwo ode $1",
        "mycontris": "Kontribusi",
        "anoncontribs": "Kontribusi",
        "contribsub2": "Ode {{GENDER:$3|$1}} ($2)",
+       "nocontribs": "Diya'a u loboli'a mohumayawa lo kriteria botiya.",
        "uctop": "(masatiya)",
        "month": "Lonto hulalo (wawu to'udiipo)",
        "year": "Lonto taawunu (wawu to'udiipo)",
        "whatlinkshere-hideimages": "$1 berkas wumbuta",
        "whatlinkshere-filters": "U'ayahu",
        "ipboptions": "2 jam:2 hours,1 huyi:1 day,3 huyi:3 days,1 diminggu:1 week,2 diminggu:2 weeks,1 hula:1 month,3 hula:3 months,6 hula:6 months,1 taawunu:1 year,layito:infinite",
+       "infiniteblock": "ja to enggade",
        "blocklink": "tangguwalo",
        "contribslink": "kontrib",
        "blocklogpage": "Bubuli log",
        "blocklogentry": "momubulo [[$1]] wolo pulito wakutu $2 $3",
+       "reblock-logentry": "momoli'o blokir [[$1]] wolo wakutu pulitiyo $2 $3",
+       "block-log-flags-nocreate": "mohutu akun pilateyaliyo",
        "proxyblocker": "Bubulo proxi",
        "movelogpage": "Log piloheyiya",
        "export": "Ekspor halaman",
        "thumbnail-more": "Po'odamanga",
+       "importlogpage": "Log impor",
        "tooltip-pt-userpage": "Halaman {{GENDER:|Yi'o ta ohu'uwo}}",
        "tooltip-pt-mytalk": "Halaman {{GENDER:|posilitamu}}",
        "tooltip-pt-preferences": "Preferensi{{GENDER:|Yi'o}}",
        "tooltip-t-recentchangeslinked": "Boheli loboli'a mola to halaman owumbuta ode halaman botiye",
        "tooltip-feed-atom": "Paalo atom ode halaman botiya",
        "tooltip-t-contributions": "Daputari kontribusi {{GENDER:$1|ta ohu'uwo botiye}}",
+       "tooltip-t-emailuser": "Lawola surel ode {{GENDER:$1|ta ohu'uwo botiye}}",
        "tooltip-t-upload": "Detohe berkas-berkas",
        "tooltip-t-specialpages": "Daputari nga'amila halaman spesial",
        "tooltip-t-print": "Persi ciletaki halaman botiye",
        "tooltip-ca-nstab-mediawiki": "Bilohi tahuli lo sistem",
        "tooltip-ca-nstab-template": "Bilohi template",
        "tooltip-ca-nstab-category": "Bilohi kategori halaman",
+       "tooltip-minoredit": "Tuwoti utiye biloli'a kiki'o",
        "tooltip-save": "Tahuwa u biloli'umu",
        "tooltip-preview": "Bilohipo u biloli'umu. Popopasiya utiye to'u diipo molahu.",
        "tooltip-diff": "Bilohi u loboli'o pilohutumu",
        "simpleantispam-label": "Momarakisa anti-spam.\n<strong>kekeya</strong> tuwangalo!",
        "pageinfo-title": "Informasi untuk \"$1\"",
        "pageinfo-header-basic": "Bungo lo habari",
+       "pageinfo-header-edits": "Riwayati lomoli'o",
        "pageinfo-header-restrictions": "Dudaha halaman",
+       "pageinfo-header-properties": "Properti halaman",
        "pageinfo-display-title": "Judul bibilohu",
+       "pageinfo-default-sort": "Tunuhe u'unti kakali",
        "pageinfo-length": "Haya'o halaman (to delomo bita)",
        "pageinfo-article-id": "ID Halaman",
        "pageinfo-language": "Bahasa tuwango halaman",
+       "pageinfo-content-model": "Model tuwango halaman",
        "pageinfo-robot-policy": "Pengindeksan monto robot",
+       "pageinfo-robot-index": "Mowali (lo'otapu ijini)",
+       "pageinfo-robot-noindex": "Ja mowali",
        "pageinfo-watchers": "Jumula lo ta hemongawasi halaman",
+       "pageinfo-few-watchers": "I'ilangi to'u $1 {{PLURAL:$1|pengunjung}}",
        "pageinfo-redirects-name": "Jumula u pilobale ode halaman botiya",
+       "pageinfo-subpages-name": "Nomoro meyalo tayadu halaman botiye",
+       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|mopobale}}; $3 {{PLURAL:$3|non-mopobale}})",
+       "pageinfo-firstuser": "Ta lohutu halaman",
        "pageinfo-firsttime": "Tanggal pilohutuwa halaman",
+       "pageinfo-lastuser": "Ta lolulade pulitiyo",
+       "pageinfo-lasttime": "Tanggal lomoli'a pulitiyo",
+       "pageinfo-edits": "Jumula nga'amila u biloli'o",
+       "pageinfo-authors": "Jumula nga'amila ta hepolulade hihihede",
        "pageinfo-recent-edits": "Jumula boheli biloli'a mola (to delomo $1 pulitiyo)",
+       "pageinfo-recent-authors": "Jumula lo ta hepolulade bohu hihihede",
+       "pageinfo-magic-words": "{{PLURAL:$1|kata}} ajaib ($1)",
+       "pageinfo-hidden-categories": "{{PLURAL:$1|dalala}} wanto-wanto'o ($1)",
+       "pageinfo-templates": "{{PLURAL:$1|templat}} yang ditransklusi ($1)",
        "pageinfo-toolboxlink": "Halaman habari",
+       "pageinfo-contentpage": "Yirekeni odelo halaman konten",
        "pageinfo-contentpage-yes": "Jo",
+       "patrol-log-page": "Log patroli",
        "previousdiff": "← Biloli'o to'udiipo",
        "nextdiff": "Biloli'o lapatiyoma'o →",
+       "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|halaman}}",
        "file-info-size": "$1 x $2 piksel, tu'udu berkas:$3, MIME tipe: $4",
+       "file-info-size-pages": "$1 × $2 piksel, ukuran berkas: $3, tipe MIME: $4, $5 {{PLURAL:$5|halaman}}",
        "file-nohires": "Diya'a resolusi damango",
        "svg-long-desc": "Berkas SGV, nominal $1 x $2 piksel, damango berkas:$3",
        "show-big-image": "Berkas asli",
        "exif-orientation-1": "Normal",
        "namespacesall": "nga'amila",
        "monthsall": "nga'amila",
+       "imgmultipagenext": "halaman wumbutiyo",
+       "imgmultigo": "Ntali",
+       "imgmultigoto": "Ntali ode halaman $1",
+       "watchlisttools-clear": "Luluta daputari he'awasiyalo",
+       "watchlisttools-view": "Popobilohe u loboli'a wayitiyo",
+       "watchlisttools-edit": "Popobilohe wawu boli'a daputari he'awasiyalo",
+       "watchlisttools-raw": "Boli'a daputari he'awasiyalo meenggo",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|bisala]])",
+       "redirect": "Lopobale payu ID berkas, ta ohu'uwo, halaman, revisi, meyalo log",
+       "redirect-summary": "Halaman istimewa botiye loheyi ode berkas (odelo tanggulo berkasliyo), halaman (sesuai ID revisinya), meyalo halaman pengguna (sesuai ID numerik penggunanya). Penggunaan: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], meyalo[[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Ntali",
        "redirect-lookup": "Yilolohu",
+       "redirect-value": "Niai",
+       "redirect-user": "ID lo ta ohu'uwo",
        "redirect-page": "ID Halaman",
        "redirect-revision": "Halaman biloli'o",
        "redirect-file": "Tanggulo berkas",
        "tag-filter": "[[Special:Tags|Tag]]filter:",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag}}]]: $2)",
        "tags-active-yes": "Jo",
+       "tags-active-no": "De'e",
+       "tags-hitcount": "$1 {{PLURAL:$1|boli'o|loboli'a}}",
        "logentry-delete-delete": "$1 {{GENDER:$2|moluluto}}halaman $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|mopowuwalingo}} halaman $3 ($4)",
+       "logentry-delete-revision": "$1 {{GENDER:$2|momoli'o}} bibilohu {{PLURAL:$5|$5  revisi}} to halaman $3: $4",
        "revdelete-content-hid": "tuwango yilanto'o",
        "logentry-move-move": "$1 {{GENDER:$2|moheyi}} halaman $3 ode $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|loheyi}} halaman $3 ode $4 ja lohutu pengalihan",
        "logentry-move-move_redir": "$1 {{GENDER:$2|loheyi}} halaman $3 ode $4 lodeehu pengalihan",
+       "logentry-patrol-patrol-auto": "$1 otomatis {{GENDER:$2|tuwoti}} biloli'o $4 lonto halaman $3 pilatroli",
        "logentry-newusers-create": "Ta ohu'uwo akun $1 {{GENDER:$2|mohutu}}",
        "logentry-newusers-autocreate": "Akun $1 {{GENDER:$2|pilohutu}} otomatis",
        "logentry-upload-upload": "$1 {{GENDER:$2|mengunggah}} $3",
+       "logentry-upload-overwrite": "$1 {{GENDER:$2|mopodetohu}} versi bohu lo $3",
        "searchsuggest-search": "Lolohe {{SITENAME}}",
        "duration-days": "$1 {{PLURAL:$1|huyi}}",
        "randomrootpage": "Halaman totonulalo"
index 6bfcda3..9c67f0b 100644 (file)
@@ -30,7 +30,8 @@
                        "Matma Rex",
                        "Bhatakati aatma",
                        "YmKavishwar",
-                       "Kevin Kovadia"
+                       "Kevin Kovadia",
+                       "Drashti4"
                ]
        },
        "tog-underline": "કડીઓની નીચે લીટી (અંડરલાઇન):",
@@ -48,6 +49,8 @@
        "tog-watchdefault": "હું ફેરફાર કરૂં તે પાનાં અને ફાઇલ્સ મારી ધ્યાનસૂચિમાં ઉમેરો",
        "tog-watchmoves": "હું ખસેડું તે પાનાં અને ફાઇલ્સ મારી ધ્યાનસૂચિમાં ઉમેરો",
        "tog-watchdeletion": "હું દૂર કરું તે પાનાં અને ફાઇલ્સ મારી ધ્યાનસૂચિમાં ઉમેરો",
+       "tog-watchuploads": "મારી જોવાનીસૂચિ પર અપલોડ કરેલી નવી ફાઇલોને ઉમેરો",
+       "tog-watchrollback": "પૃષ્ઠોને ઉમેરો જ્યાં મેં મારી જોવાની સૂચિમાં રોલબેક કર્યું છે",
        "tog-minordefault": "બધા નવા ફેરફારો નાના તરીકે માર્ક કરો.",
        "tog-previewontop": "ફેરફાર પેટી પહેલાં પૂર્વાલોકન દર્શાવો",
        "tog-previewonfirst": "પ્રથમ ફેરફાર વખતે પૂર્વાલોકન બતાવો",
        "tog-shownumberswatching": "ધ્યાન રાખતા સભ્યોની સંખ્યા બતાવો",
        "tog-oldsig": "તમારા હાલના હસ્તાક્ષર:",
        "tog-fancysig": "હસ્તાક્ષરનો વિકિલખાણ તરીકે ઉપયોગ કરો (સ્વચાલિત કડી વગર)",
-       "tog-uselivepreview": "àª\9cà«\80વàª\82ત àªªà«\82રà«\8dવદરà«\8dશન àªµàª¾àªªàª°ો",
+       "tog-uselivepreview": "પà«\83ષà«\8dઠ àª«àª°à«\80થà«\80 àª²à«\8bડ àª\95રà«\8dયા àªµàª\97ર àªªà«\82રà«\8dવાવલà«\8bàª\95નà«\8b àª¬àª¤àª¾àªµો",
        "tog-forceeditsummary": "કોરો 'ફેરફાર સારાંશ' ઉમેરતા પહેલા મને ચેતવો",
        "tog-watchlisthideown": "'મારી ધ્યાનસુચી'માં મે કરેલા ફેરફારો છુપાવો",
        "tog-watchlisthidebots": "ધ્યાનસુચીમાં બોટ દ્વારા થયેલા ફેરફાર સંતાડો.",
        "tog-watchlisthideminor": "'મારી ધ્યાનસુચી'માં નાનાં ફેરફારો છુપાવો",
        "tog-watchlisthideliu": "લોગ થયેલા સભ્ય દ્વારા કરવામાં આવેલ ફેરફાર ધ્યાનસુચીમાં છુપાવો.",
+       "tog-watchlistreloadautomatically": "જ્યારેપણ ફિલ્ટર બદલાઈ જાય ત્યારે આપમેળે જોવાની સૂચિ ફરીથી લોડ કરો (JavaScript આવશ્યક છે)",
+       "tog-watchlistunwatchlinks": "દૃશ્યયાદી પ્રવેશોમાં સીધી નિષ્ક્રિય/દૃશ્ય લિંક્સ ઉમેરો (ટૉગલ કાર્યક્ષમતા માટે આવશ્યક JavaScript)",
        "tog-watchlisthideanons": "અજાણ્યા સભ્ય દ્વારા થયેલ ફેરફાર મારી ધ્યાનસુચીમાં છુપાવો",
        "tog-watchlisthidepatrolled": "સુરક્ષા કાજે કરવામાં આવેલ ફેરફાર મારી ધ્યાનસુચીમાં છુપાવો",
        "tog-watchlisthidecategorization": "પાનાંઓનું વર્ગીકરણ છુપાવો",
        "october-date": "ઓક્ટોબર $1",
        "november-date": "નવેમ્બર $1",
        "december-date": "ડિસેમ્બર $1",
+       "period-am": "સવારે",
+       "period-pm": "સાંજે",
        "pagecategories": "{{PLURAL:$1|શ્રેણી|શ્રેણીઓ}}",
        "category_header": "શ્રેણી \"$1\" ના પાનાં",
        "subcategories": "ઉપશ્રેણીઓ",
        "recentchangeslinked-feed": "આની સાથે જોડાયેલા ફેરફાર",
        "recentchangeslinked-toolbox": "આની સાથે જોડાયેલા ફેરફાર",
        "recentchangeslinked-title": "\"$1\" ને લગતા ફેરફારો",
-       "recentchangeslinked-summary": "આ એવા ફેરફારોની યાદી છે જે આ ચોક્કસ પાના (કે શ્રેણીનાં સભ્ય પાનાઓ) સાથે જોડાયેલા પાનાઓમાં તાજેતરમાં કરવામાં આવ્યા હોય.\n[[Special:Watchlist|તમારી ધ્યાનસૂચિમાં]] હોય તેવા પાનાં '''ઘાટા અક્ષર'''માં વર્ણવ્યાં છે.",
+       "recentchangeslinked-summary": "આ એવા ફેરફારોની યાદી છે જે આ ચોક્કસ પાના (કે શ્રેણીનાં સભ્ય પાનાઓ) સાથે જોડાયેલા પાનાઓમાં તાજેતરમાં કરવામાં આવ્યા હોય.\n[[Special:Watchlist|તમારી ધ્યાનસૂચિમાં]] હોય તેવા પાનાં <strong>ઘાટા અક્ષર</strong>માં વર્ણવ્યાં છે.",
        "recentchangeslinked-page": "પાનાનું નામ:",
        "recentchangeslinked-to": "આને બદલે આપેલા પાનાં સાથે જોડાયેલા લેખોમાં થયેલા ફેરફારો શોધો",
        "upload": "ફાઇલ ચડાવો",
        "apisandbox-examples": "ઉદાહરણો",
        "apisandbox-results": "પરિણામો",
        "booksources": "પુસ્તક સ્રોત",
-       "booksources-search-legend": "પà«\81સà«\8dતàª\95 àª¸à«\8dરà«\8bત àª®àª¾àª\9fà«\87 àª¶à«\8bધà«\8b",
+       "booksources-search-legend": "પુસ્તક સ્રોત શોધો",
        "booksources-isbn": "આઇએસબીએન:",
        "booksources-search": "શોધ",
        "booksources-text": "નીચે દર્શાવેલ યાદી એ કડીઓ બતાવે છે જેઓ નવા અને જૂના પુસ્તકો  વેચે છે , અને તમે માંગેલ વસ્તુ સંબંધિ વધુ મહિતી પણ ધરાવી શકે છે.",
        "deletecomment": "કારણ:",
        "deleteotherreason": "અન્ય/વધારાનું કારણ:",
        "deletereasonotherlist": "અન્ય કારણ",
-       "deletereason-dropdown": "* àª¦à«\82ર àª\95રવાના àª¸àª¾àª®àª¾àª¨à«\8dય àª\95ારણà«\8b\n** àª¸à«\8dપામ\n** àª­àª¾àª\82àª\97ફà«\8bડà«\80યા àªªà«\8dરવà«\83તà«\8dતિ\n** àªªà«\8dરàª\95ાશનાધિàª\95ાર àª­àª\82àª\97 \n** àª²à«\87àª\96àª\95નà«\80 àªµàª¿àª¨àª\82તà«\80\n** àª­àª¾àª\82àª\97à«\87લ àªµàª³àª¾àª\82àª\95",
+       "deletereason-dropdown": "* àª¦à«\82ર àª\95રવાના àª¸àª¾àª®àª¾àª¨à«\8dય àª\95ારણà«\8b\n** àª¸à«\8dપામ\n** àª­àª¾àª\82àª\97ફà«\8bડà«\80યા àªªà«\8dરવà«\83તà«\8dતિ\n** àªªà«\8dરàª\95ાશનાધિàª\95ાર àª­àª\82àª\97 \n** àª²à«\87àª\96àª\95નà«\80 àªµàª¿àª¨àª\82તà«\80\n** àª­àª¾àª\82àª\97à«\87લ àª¦àª¿àª¶àª¾àª¨àª¿àª°à«\8dદà«\87શ",
        "delete-edit-reasonlist": "ભુંસવાનું કારણ બદલો.",
        "delete-toobig": "આ પાનાના ફેરફારોનો ઇતિહાસ ખૂબ લાંબો છે , $1 {{PLURAL:$1|ફેરફાર|ફેરફારો}}થી પણ વધારે.\n{{SITENAME}}ને અક્સ્માતે ખોરવાતું અટકાવવા આવા પાનાને હટાવવા પર પ્રતિબંધ છે.",
        "delete-warning-toobig": "આ પાનાનો ઇતિહાસ ઘણો લાંબો છે લગભગ  $1 {{PLURAL:$1|ફેરફાર|ફેરફારો}}.\nતેને ભૂંસતા {{SITENAME}}ના માહિતીસંચને લાગતા કામકાજ પર અસર થૈ શકે છે;\nસંભાળ પૂર્વક આગળ વધો.\n\n\nDeleting it may disrupt database operations of {{SITENAME}};",
        "version-libraries-library": "લાઇબ્રેરી",
        "version-libraries-version": "આવૃત્તિ",
        "redirect": "ફાઇલ, સભ્ય, પાનું, આવૃત્તિ, અથવા લૉગ ઓળખ વડે દિશાનિર્દેશન",
+       "redirect-summary": "આ વિશિષ્ટ પૃષ્ઠ ફાઇલને (ફાઇલ નામ આપવામાં આવે છે), એક પૃષ્ઠ (પુનરાવર્તન ID અથવા પૃષ્ઠ ID આપવામાં આવે છે), વપરાશકર્તા પૃષ્ઠ (એક આંકડાકીય વપરાશકર્તા ID આપવામાં આવે છે), અથવા લોગ એન્ટ્રી (લોગ ID ને આપવામાં આવે છે) પર પુનર્નિર્દેશન કરે છે. વપરાશ:[[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], અથવા [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "જાઓ",
        "redirect-lookup": "જુઓ:",
        "redirect-value": "કિંમત:",
index 86e2005..e1a9271 100644 (file)
        "rcfilters-empty-filter": "אין מסננים פעילים. כל התרומות מוצגות.",
        "rcfilters-filterlist-title": "מסננים",
        "rcfilters-filterlist-whatsthis": "איך הם עובדים?",
-       "rcfilters-filterlist-feedbacklink": "ש×\9c×\99×\97ת ×\9eש×\95×\91 ×¢×\9c ×\94×\9eסננ×\99×\9d ×\94×\97×\93ש×\99×\9d (×\91×\98×\90)",
+       "rcfilters-filterlist-feedbacklink": "×\91×\90פשר×\95ת×\9a ×\9cספר ×\9c× ×\95 ×\9e×\94 ×\93עת×\9a ×¢×\9c ×\9b×\9c×\99 ×\94ס×\99× ×\95×\9f (×\94×\97×\93ש×\99×\9d) ×\94×\90×\9c×\94",
        "rcfilters-highlightbutton-title": "הבלטת התוצאות",
        "rcfilters-highlightmenu-title": "בחירת צבע",
        "rcfilters-highlightmenu-help": "בחירת צבע להדגשת מאפיין זה",
        "rcfilters-liveupdates-button-title-on": "כיבוי העדכונים החיים",
        "rcfilters-liveupdates-button-title-off": "הצגת שינויים חדשים כשהם מתרחשים",
        "rcfilters-watchlist-markSeen-button": "סימון כל השינויים כאילו נצפו",
+       "rcfilters-watchlist-editWatchlist-button": "עריכת רשימת הדפים במעקב שלך",
+       "rcfilters-watchlist-showupdated": "שינויים בדפים שלא ביקרת בהם מאז ביצוע השינויים מופיעים בכתב <strong>מודגש</strong>, ומודגשים בצבע.",
        "rcnotefrom": "להלן {{PLURAL:$5|השינוי שבוצע|השינויים שבוצעו}} מאז <strong>$3, $4</strong> (מוצגים עד <strong>$1</strong>).",
        "rclistfromreset": "איפוס בחירת התאריך",
        "rclistfrom": "הצגת שינויים חדשים החל מ־$2, $3",
index 075c3f3..e06895e 100644 (file)
        "rcfilters-empty-filter": "कोई सक्रिय फिल्टर नहीं। सभी योगदान दिखाए गए है।",
        "rcfilters-filterlist-title": "फिल्टर",
        "rcfilters-filterlist-whatsthis": "यह कैसे कार्य करता है?",
-       "rcfilters-filterlist-feedbacklink": "नà¤\8f (बà¥\80à¤\9fा) à¤«à¤¿à¤²à¥\8dà¤\9fर à¤ªà¤° à¤ªà¥\8dरतिà¤\95à¥\8dरिया à¤¦à¥\87ं",
+       "rcfilters-filterlist-feedbacklink": "हमà¥\87à¤\82 à¤¬à¤¤à¤¾à¤\8fà¤\82 à¤\95ि à¤\86प à¤\87न (नयà¥\87) à¤¨à¤¿à¤¸à¥\8dपà¤\82दन à¤\94à¤\9cारà¥\8bà¤\82 à¤\95à¥\87 à¤¬à¤¾à¤°à¥\87 à¤®à¥\87à¤\82 à¤\95à¥\8dया à¤¸à¥\8bà¤\9aतà¥\87 à¤¹à¥\88ं",
        "rcfilters-highlightbutton-title": "Highlight results",
        "rcfilters-highlightmenu-title": "रंग चुनें",
        "rcfilters-highlightmenu-help": "इस गुण को हाइलाइट करने के लिए एक रंग चुनें",
        "rcfilters-liveupdates-button-title-on": "जीवंत अद्यतन बंद करें",
        "rcfilters-liveupdates-button-title-off": "नये परिवर्तन प्रदर्शित करें जैसे ही वे घटित होते हैं",
        "rcfilters-watchlist-markSeen-button": "देखे गए के रूप में सभी परिवर्तनों को चिह्नित करें",
+       "rcfilters-watchlist-editWatchlist-button": "देखे गए पन्नों की सूची को संपादित करें",
+       "rcfilters-watchlist-showupdated": "उन पन्नों में परिवर्तन जिनपर आप परिवर्तन के बाद से नहीं गए हैं, ठोस चिन्ह के साथ <strong>bold</strong> दिखाए गए हैं।",
        "rcnotefrom": "नीचे <strong>$2</strong> के बाद से (<strong>$1</strong> तक) {{PLURAL:$5|हुआ बदलाव दर्शाया गया है|हुए बदलाव दर्शाए गये हैं}}।",
        "rclistfromreset": "चुने दिनांक पहले जैसा करें",
        "rclistfrom": "$3 $2 से नये बदलाव दिखाएँ",
index 0b67bc9..aa598a2 100644 (file)
        "dberr-outofdate": "Imajte na umu da su njihova kazala našeg sadržaja možda zastarjela.",
        "dberr-cachederror": "Sljedeće je dohvaćena kopija tražene stranice, te možda nije ažurirana.",
        "htmlform-invalid-input": "Postoje problemi s dijelom Vašeg unosa",
-       "htmlform-select-badoption": "Vrijednost koju ste naveli nije ispravan izbor.",
+       "htmlform-select-badoption": "Vrijednost koju ste naveli nije vrijedeća mogućnost.",
        "htmlform-int-invalid": "Vrijednost koju ste naveli nije cijeli broj.",
        "htmlform-float-invalid": "Vrijednost koju ste naveli nije broj.",
        "htmlform-int-toolow": "Vrijednost koju ste naveli je ispod minimuma od $1",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2; $3 %)",
        "mediastatistics-bytespertype": "Ukupna veličina datoteka za ovaj odlomak: {{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2; $3%).",
        "mediastatistics-allbytes": "Ukupna veličina svih datoteka: {{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2).",
-       "mediastatistics-header-unknown": "Nepoznato",
+       "mediastatistics-header-unknown": "Nepoznata",
        "mediastatistics-header-bitmap": "Rasterske slike",
        "mediastatistics-header-drawing": "Crteži (vektorske slike)",
        "mediastatistics-header-audio": "Zvukovne datoteke",
-       "mediastatistics-header-video": "Video datoteke",
+       "mediastatistics-header-video": "Videodatoteke",
        "mediastatistics-header-multimedia": "Datoteke digitalnih kontejnerskih formata",
        "mediastatistics-header-office": "Datoteke uredskih formata",
        "mediastatistics-header-text": "Tekstualne datoteke",
        "mw-widgets-mediasearch-noresults": "Nema rezultata.",
        "mw-widgets-titleinput-description-new-page": "stranica još ne postoji",
        "mw-widgets-titleinput-description-redirect": "preusmjeravanje na $1",
+       "mw-widgets-usersmultiselect-placeholder": "Dodaj još...",
        "date-range-from": "Od nadnevka:",
        "date-range-to": "Do nadnevka:",
        "randomrootpage": "Slučajna korijenska stranica",
index 2ae1aa3..3ebee17 100644 (file)
        "anontalk": "Diskusija",
        "navigation": "Nawigacija",
        "and": "&#32;a",
-       "qbfind": "Namakać",
-       "qbbrowse": "Přepytować",
-       "qbedit": "Wobdźěłać",
-       "qbpageoptions": "Tuta strona",
-       "qbmyoptions": "Moje strony",
        "faq": "Husto stajene prašenja (FAQ)",
-       "faqpage": "Project:Husto stajene prašenja (FAQ)",
        "actions": "Akcije",
        "namespaces": "Mjenowe rumy",
        "variants": "Warianty",
        "edit-local": "Lokalny wopis wobdźěłać .",
        "create": "Wutworić",
        "create-local": "Lokalny wopis přidać",
-       "editthispage": "Tutu stronu wobdźěłać",
-       "create-this-page": "Stronu wutworić",
        "delete": "wušmórnyć",
-       "deletethispage": "Stronu wušmórnyć",
-       "undeletethispage": "Tutu stronu wobnowić",
        "undelete_short": "{{PLURAL:$1|jednu wersiju|$1 wersiji|$1 wersije|$1 wersijow}} wobnowić",
        "viewdeleted_short": "{{PLURAL:$1|jednu wušmórnjenu změnu|$1 wušmórnjenej změnje|$1 wušmórnjene změny|$1 wušmórnjenych změnow}} sej wobhladać",
        "protect": "škitać",
        "protect_change": "změnić",
-       "protectthispage": "Stronu škitać",
        "unprotect": "Škit wotstronić",
-       "unprotectthispage": "Tutu stronu hižo nješkitać",
        "newpage": "Nowa strona",
-       "talkpage": "Tutu stronu diskutować",
        "talkpagelinktext": "diskusija",
        "specialpage": "Specialna strona",
        "personaltools": "Wosobinske nastroje",
-       "articlepage": "Nastawk",
        "talk": "diskusija",
        "views": "Zwobraznjenja",
        "toolbox": "Nastroje",
        "tool-link-emailuser": "{{GENDER:$1|Tutomu wužiwarjej|Tutej wužiwarce}} mejlku pósłać",
-       "userpage": "Wužiwarsku stronu pokazać",
-       "projectpage": "Projektowu stronu pokazać",
        "imagepage": "Datajowu stronu sej wobhladać",
        "mediawikipage": "Zdźělenku pokazać",
        "templatepage": "Předłohu pokazać",
        "rcfilters-invalid-filter": "Njepłaćiwy filter",
        "rcfilters-filterlist-whatsthis": "Što je to?",
        "rcfilters-highlightmenu-title": "Barbu wubrać",
-       "rcfilters-filtergroup-registration": "Registrowanje wužiwarja",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nowački",
        "rcfilters-filter-user-experience-level-newcomer-description": "Mjenje hač 10 změnow a štyri aktiwne dny.",
        "rcfilters-filter-user-experience-level-learner-label": "Započatkarjo",
        "booksources-search": "Pytać",
        "booksources-text": "To je lisćina wotkazow k druhim sydłam, kotrež nowe a trjebane knihi předawaja. Tam móžeš tež dalše informacije wo knihach dóstać, kotrež pytaš:",
        "booksources-invalid-isbn": "Podate ISBN-čisło njezda so płaćiwe być; přepruwuj za zmylkami, z tym zo z orginialneho žórła kopěruješ.",
+       "magiclink-tracking-isbn": "Strony, kotrež wužiwaja magiske ISBN-wotkazy",
        "specialloguserlabel": "Wukonjer:",
        "speciallogtitlelabel": "Cil (titul abo {{ns:user}}:wužiwarske mjeno za wužiwarja):",
        "log": "Protokole",
        "fileduplicatesearch-noresults": "Žana dataja z mjenom \"$1\" namakana.",
        "specialpages": "Specialne strony",
        "specialpages-note-top": "Legenda",
-       "specialpages-note": "* Normalne specialne strony.\n* <span class=\"mw-specialpagerestricted\">Specialne strony z wobmjezowanym přistupom.</span>",
        "specialpages-group-maintenance": "Hladanske lisćiny",
        "specialpages-group-other": "Druhe specialne strony",
        "specialpages-group-login": "Přizjewić/Konto załožić",
        "compare-invalid-title": "Titul, kotryž sy podał, je njepłaćiwy.",
        "compare-title-not-exists": "Titul, kotryž sy podał, njeeksistuje.",
        "compare-revision-not-exists": "Wersija, kotruž sy podał, njeeksistuje.",
+       "diff-form": "'''formular'''",
        "dberr-problems": "Wodaj! Tute sydło ma techniske ćežkosće.",
        "dberr-again": "Počakń někotre mjeńšiny a zaktualizuj stronu.",
        "dberr-info": "(Njeje móžno na datowu banku přistup měć: $1)",
index 5480a2e..9e4317b 100644 (file)
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (vide etiam le [[Special:NewPages|lista de nove paginas]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Monstrar",
+       "rcfilters-tag-remove": "Remover '$1'",
        "rcfilters-legend-heading": "<strong>Lista de abbreviationes:</strong>",
        "rcfilters-other-review-tools": "<strong>Altere instrumentos de revision</strong>",
        "rcfilters-group-results-by-page": "Gruppar resultatos per pagina",
index c5f8303..b3a3fd6 100644 (file)
        "tog-watchlisthideminor": "Nascondi le modifiche minori negli osservati speciali",
        "tog-watchlisthideliu": "Nascondi le modifiche degli utenti registrati negli osservati speciali",
        "tog-watchlistreloadautomatically": "Ricarica automaticamente l'elenco degli osservati speciali ogni volta che si modifica un filtro (richiede JavaScript)",
+       "tog-watchlistunwatchlinks": "Aggiungi collegamenti diretti per seguire/non seguire gli elementi negli osservati speciali (richiede JavaScript per utilizzare questa funzionalità)",
        "tog-watchlisthideanons": "Nascondi le modifiche degli utenti anonimi negli osservati speciali",
        "tog-watchlisthidepatrolled": "Nascondi le modifiche verificate negli osservati speciali",
        "tog-watchlisthidecategorization": "Nascondi la categorizzazione delle pagine",
        "rcfilters-tag-remove": "Rimuovi '$1'",
        "rcfilters-legend-heading": "<strong>Elenco di abbreviazioni:</strong>",
        "rcfilters-other-review-tools": "<strong>Altri strumenti di revisione</strong>",
+       "rcfilters-group-results-by-page": "Raggruppa risultati per pagina",
        "rcfilters-grouping-title": "Raggruppamento",
        "rcfilters-activefilters": "Filtri attivi",
        "rcfilters-advancedfilters": "Filtri avanzati",
        "rcfilters-hours-title": "Ore recenti",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|giorno|giorni}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|ora|ore}}",
+       "rcfilters-highlighted-filters-list": "Evidenziato: $1",
        "rcfilters-quickfilters": "Filtri salvati",
        "rcfilters-quickfilters-placeholder-title": "Nessun collegamento salvato ancora",
        "rcfilters-quickfilters-placeholder-description": "Per salvare le impostazioni del tuo filtro e riutilizzarle dopo, clicca l'icona segnalibro nell'area \"Filtri attivi\" qui sotto",
        "rcfilters-savedqueries-new-name-label": "Nome",
        "rcfilters-savedqueries-new-name-placeholder": "Descrivi lo scopo del filtro",
        "rcfilters-savedqueries-apply-label": "Crea filtro",
+       "rcfilters-savedqueries-apply-and-setdefault-label": "Crea filtro predefinito",
        "rcfilters-savedqueries-cancel-label": "Annulla",
        "rcfilters-savedqueries-add-new-title": "Salva le impostazioni attuali del filtro",
        "rcfilters-restore-default-filters": "Ripristina i filtri predefiniti",
        "rcfilters-empty-filter": "Nessun filtro attivo. Sono mostrati tutti i contributi.",
        "rcfilters-filterlist-title": "Filtri",
        "rcfilters-filterlist-whatsthis": "Come funzionano?",
-       "rcfilters-filterlist-feedbacklink": "Lascia un commento sulla nuova funzionalità sperimentale",
+       "rcfilters-filterlist-feedbacklink": "Dicci cosa ne pensi su questi (nuovi) strumenti di filtraggio",
        "rcfilters-highlightbutton-title": "Evidenzia risultati",
        "rcfilters-highlightmenu-title": "Seleziona un colore",
        "rcfilters-highlightmenu-help": "Seleziona un colore per evidenziare questa proprietà",
        "rcfilters-filter-previousrevision-description": "Tutte le modifiche che non sono la \"versione attuale\".",
        "rcfilters-filter-excluded": "Escluso",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:non</strong> $1",
-       "rcfilters-exclude-button-off": "Escludi selezionato",
-       "rcfilters-exclude-button-on": "Escludendo selezionato",
+       "rcfilters-exclude-button-off": "Escludi selezionati",
+       "rcfilters-exclude-button-on": "Escludendo selezionati",
        "rcfilters-view-advanced-filters-label": "Filtri avanzati",
        "rcfilters-view-tags": "Modifiche etichettate",
+       "rcfilters-view-namespaces-tooltip": "Filtra risultati per namespace",
+       "rcfilters-view-tags-tooltip": "Filtra risultati per etichette di modifica",
+       "rcfilters-view-return-to-default-tooltip": "Torna al menu filtri principale",
+       "rcfilters-watchlist-editWatchlist-button": "Modifica il tuo elenco di pagine osservate",
        "rcnotefrom": "Di seguito {{PLURAL:$5|è elencata la modifica apportata|sono elencate le modifiche apportate}} a partire da <strong>$3, $4</strong> (mostrate fino a <strong>$1</strong>).",
        "rclistfromreset": "Reimposta la selezione della data",
        "rclistfrom": "Mostra le nuove modifiche a partire daː $2, $3",
        "enotif_lastdiff": "Per vedere questa modifica, vedi $1",
        "enotif_anon_editor": "utente anonimo $1",
        "enotif_body": "Gentile $WATCHINGUSERNAME,\n\n$PAGEINTRO $NEWPAGE\n\nOggetto dell'intervento, inserito dall'autore: $PAGESUMMARY $PAGEMINOREDIT\n\nContatta l'autore:\nvia posta elettronica: $PAGEEDITOR_EMAIL\nsul sito: $PAGEEDITOR_WIKI\n\nNon verranno inviate altre notifiche in caso di ulteriori attività, se non visiti la pagina dopo aver effettuato l'accesso. Inoltre, è possibile modificare le impostazioni di notifica per tutte le pagine nella lista degli osservati speciali.\n\nIl sistema di notifica di {{SITENAME}}, al tuo servizio\n\n--\nPer modificare le impostazioni delle notifiche via posta elettronica, visita \n{{canonicalurl:{{#special:Preferences}}}}\n\nPer modificare la lista degli osservati speciali, visita \n{{canonicalurl:{{#special:EditWatchlist}}}}\n\nPer rimuovere la pagina dalla lista degli osservati speciali, visita\n$UNWATCHURL\n\nPer commentare e ricevere aiuto:\n$HELPPAGE",
+       "enotif_minoredit": "Questa è una modifica minore",
        "created": "creata",
        "changed": "modificata",
        "deletepage": "Cancella pagina",
index d94dfb6..5c9498a 100644 (file)
        "tog-shownumberswatching": "ページをウォッチしている利用者数を表示",
        "tog-oldsig": "既存の署名:",
        "tog-fancysig": "署名をウィキ文として扱う (自動リンクなし)",
-       "tog-uselivepreview": "ã\83©ã\82¤ã\83\96ã\83\97ã\83¬ã\83\93ã\83¥ã\83¼ã\82\92使ç\94¨",
+       "tog-uselivepreview": "ã\83\9aã\83¼ã\82¸ã\82\92å\86\8d読込ã\81\9bã\81\9aã\81«ã\83\97ã\83¬ã\83\93ã\83¥ã\83¼ã\82\92表示",
        "tog-forceeditsummary": "要約欄が空欄の場合に確認を促す",
        "tog-watchlisthideown": "自分の編集をウォッチリストに表示しない",
        "tog-watchlisthidebots": "ボットによる編集をウォッチリストに表示しない",
        "contentmodelediterror": "コンテンツモデルが <code>$1</code> であるため、この版を編集することができません。ページの現在のコンテンツモデルは <code>$2</code> です。",
        "recreate-moveddeleted-warn": "<strong>警告: 以前削除されたページを再作成しようとしています。</strong>\n\nこのページの編集を続行するのが適切かどうかご確認ください。\n参考までに、このページの削除と移動の記録を以下に示します:",
        "moveddeleted-notice": "このページは削除されています。\n参考のため、このページの削除と移動の記録を以下に表示します。",
-       "moveddeleted-notice-recent": "ç\94³ã\81\97訳ã\81\82ã\82\8aã\81¾ã\81\9bã\82\93ã\80\82ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81¯æ\9c\80è¿\91ï¼\8824æ\99\82é\96\93以å\86\85ï¼\89ã\81«å\89\8aé\99¤ã\81\95ã\82\8cã\81¾ã\81\97ã\81\9fã\80\82å\8f\82è\80\83ã\81®ã\81\9fã\82\81ã\80\81ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81®å\89\8aé\99¤ã\81¨移動の記録を以下に表示します。",
+       "moveddeleted-notice-recent": "ç\94³ã\81\97訳ã\81\82ã\82\8aã\81¾ã\81\9bã\82\93ã\80\82ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81¯æ\9c\80è¿\91ï¼\8824æ\99\82é\96\93以å\86\85ï¼\89ã\81«å\89\8aé\99¤ã\81\95ã\82\8cã\81¾ã\81\97ã\81\9fã\80\82å\8f\82è\80\83ã\81®ã\81\9fã\82\81ã\80\81ã\81\93ã\81®ã\83\9aã\83¼ã\82¸ã\81®å\89\8aé\99¤ã\80\81ä¿\9dè­·ã\80\81移動の記録を以下に表示します。",
        "log-fulllog": "完全な記録を閲覧",
        "edit-hook-aborted": "フックによって編集が破棄されました。\n理由は不明です。",
        "edit-gone-missing": "ページを更新できませんでした。\n既に削除されているようです。",
        "prefs-editwatchlist-clear": "ウォッチリストの全消去",
        "prefs-watchlist-days": "ウォッチリストの表示日数:",
        "prefs-watchlist-days-max": "最大 $1 {{PLURAL:$1|日間}}",
-       "prefs-watchlist-edits": "ウォッチリストの展開時の最大表示件数:",
+       "prefs-watchlist-edits": "ウォッチリストの最大表示件数:",
        "prefs-watchlist-edits-max": "最大数: 1000",
        "prefs-watchlist-token": "ウォッチリストのトークン:",
        "prefs-misc": "その他",
        "special-characters-title-endash": "en ダッシュ",
        "special-characters-title-emdash": "em ダッシュ",
        "special-characters-title-minus": "マイナス記号",
-       "mw-widgets-dateinput-no-date": "選択されたデータ無し",
+       "mw-widgets-dateinput-no-date": "日付未選択",
        "mw-widgets-mediasearch-input-placeholder": "メディアを検索",
        "mw-widgets-mediasearch-noresults": "見つかりませんでした。",
        "mw-widgets-titleinput-description-new-page": "ページは存在しません",
index fd55c88..17b1083 100644 (file)
        "emailtarget": "Lebokaké jeneng panganggo panampa",
        "emailusername": "Jeneng panganggo:",
        "emailusernamesubmit": "Kirim",
-       "email-legend": "KIrim layang-èl nyang panganggo  {{SITENAME}} liyané",
+       "email-legend": "Kirim layang-èl menyang panganggo {{SITENAME}} liyané",
        "emailfrom": "Saka:",
        "emailto": "Kanggo:",
        "emailsubject": "Prekara:",
        "protectedarticle": "ngreksa \"[[$1]]\"",
        "modifiedarticleprotection": "ngowahi tataran rereksané \"[[$1]]\"",
        "unprotectedarticle": "nyingkiraké panjagan saka \"[[$1]]\"",
-       "movedarticleprotection": "ngalih setèlan rereksan saka \"[[$2]]\" nyang \"[[$1]]\"",
+       "movedarticleprotection": "ngalih setèlan rereksan saka \"[[$2]]\" menyang \"[[$1]]\"",
        "protectedarticle-comment": "{{GENDER:$2|Ngreksa}} \"[[$1]]\"",
        "modifiedarticleprotection-comment": "{{GENDER:$2|Ngowahi tataran rereksan}} tumrap \"[[$1]]\"",
        "unprotectedarticle-comment": "{{GENDER:$2|Nyopot rereksan}} saka \"[[$1]]\"",
        "whatlinkshere": "Sing nggayut mréné",
        "whatlinkshere-title": "Kaca mawa pranala nggayut \"$1\"",
        "whatlinkshere-page": "Kaca:",
-       "linkshere": "Kaca-kaca ing ngisor iki nggayut nyang '''[[:$1]]''':",
+       "linkshere": "Kaca-kaca ing ngisor iki nggayut menyang <strong>[[:$1]]</strong>:",
        "nolinkshere": "Ora ana kaca sing nduwé pranala menyang '''[[:$1]]'''.",
        "nolinkshere-ns": " Ora ana kaca sing nduwé pranala menyang '''[[:$1]]''' ing bilik jeneng sing kapilih.",
        "isredirect": "kaca lih-lihan",
        "export-addns": "Tambah",
        "export-download": "Simpen dadi barkas",
        "export-templates": "Lebokaké cithakan",
-       "export-pagelinks": "Lebokaké kaca sing kagayut nyang jeroning:",
+       "export-pagelinks": "Lebokaké kaca sing kagayut ing sajeroning:",
        "export-manual": "Tambah kaca kanthi manual:",
        "allmessages": "Layang sistem",
        "allmessagesname": "Jeneng",
        "tooltip-ca-delete": "Busak kaca iki",
        "tooltip-ca-undelete": "Balèkna suntingan ing kaca iki sadurungé kaca iki dibusak",
        "tooltip-ca-move": "Lih kaca iki",
-       "tooltip-ca-watch": "Tambahaké kaca iki nyang pawawangan sapéyan",
+       "tooltip-ca-watch": "Tambahaké kaca iki menyang pawawangané panjenengan",
        "tooltip-ca-unwatch": "Busak kaca iki saka pawawanganing sampéyan",
        "tooltip-search": "Golèk ing {{SITENAME}}",
        "tooltip-search-go": "Jujug kaca asesirah persis mangkéné yèn ana",
        "tooltip-preview": "Pratuduhing owah-owahaning sampéyan. Anggoa cara iki sadurungé nyimpen.",
        "tooltip-diff": "Tuduhaké owah-owahan endi sing sampéyan gawé tumrap tulisan iki",
        "tooltip-compareselectedversions": "Delengen prabédan antara rong vèrsi kaca iki sing dipilih.",
-       "tooltip-watch": "Wuwuh kaca iki nyang pawawanganing sampéyan",
+       "tooltip-watch": "Wuwuh kaca iki menyang pawawangané panjenengan",
        "tooltip-watchlistedit-normal-submit": "Busak sesirah",
        "tooltip-watchlistedit-raw-submit": "Anyari daptar pangawasan",
        "tooltip-recreate": "Gawéa kaca iki manèh senadyan tau dibusak",
        "confirm-unwatch-button": "Oké",
        "confirm-unwatch-top": "Singkiraké kaca iki saka daptar pangawasan Sampéyan?",
        "confirm-rollback-button": "YA",
-       "confirm-rollback-top": "Pulihaké besutan nyang kaca iki?",
+       "confirm-rollback-top": "Pulihaké besutan menyang kaca iki?",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← kaca sadurungé",
        "imgmultipagenext": "kaca sabanjuré →",
        "pagelang-reason": "Alesan",
        "pagelang-submit": "Kirim",
        "pagelang-nonexistent-page": "Kaca $1 ora ana.",
-       "pagelang-unchanged-language": "Kaca $1 wis disetèl nyang basa $2.",
+       "pagelang-unchanged-language": "Kaca $1 wis disetèl menyang basa $2.",
        "mediastatistics-table-mimetype": "Jinis MIME",
        "mediastatistics-table-extensions": "Èkstènsi sing mungkin",
        "mediastatistics-table-count": "Cacah barkas",
index e17a7ea..d5bd1ab 100644 (file)
        "category-empty": "<em>Taggayt agi ur tesɛa asebtar, adu-taggayt neɣ afaylu agetmedia.</em>",
        "hidden-categories": "{{PLURAL:$1|Taggayt yeffren|Taggayin yeffren}}",
        "hidden-category-category": "Taggayin yeffren",
-       "category-subcat-count": "Taggayt agi tesɛa {{PLURAL:$2|adu-taggayt|$2 adu-taggayin, ɣef ayed {{PLURAL:$1|t-agi|t-igi $1}}}} ddaw agi.",
-       "category-subcat-count-limited": "Taggayt agi tesɛa {{PLURAL:$1|adu-taggayt agi|tid $1 adu-taggayin agi}} ddaw-agi.",
-       "category-article-count": "Taggayt agi tesɛa {{PLURAL:$2|asebter agi|$2 isebtaren, ɣef ayed {{PLURAL:$1|t-agi|t-igi $1}} ddaw-agi}}.",
+       "category-subcat-count": "Taggayt-agi {{PLURAL:$2|0=ur tegbir ula d yiwet n taggayt tasnawt|1=tegber kan taggayt tasnawant ddaw-a|tegber $2 n taggayin tisnawanin, gar-asent {{PLURAL:$1|0=ula d yiwet|1=tin|tigad $1}} ddaw-a}}.",
+       "category-subcat-count-limited": "Taggayt-agi tegber {{PLURAL:$1|n taggayt tasnawant|$1 n taggayin tisnawanin}} ddaw-a.",
+       "category-article-count": "Taggayt-agi{{PLURAL:$2|0=ur tegbur ula d yiwen n usebtert|1=tegber kan yiwen n usebterddaw-a|tegber $2 n isebtar, gar-asen {{PLURAL:$1|0=ula d yiwen|1=tin| $1}} n ddaw-a}}.",
        "category-article-count-limited": "{{PLURAL:$1|Asebter agi yella|$1 isebtar agi llan}} deg taggayt agi.",
        "category-file-count": "Taggayt agi tesɛa {{PLURAL:$2|afaylu agi|$2 ifuyla, ɣef ayed {{PLURAL:$1|t-agi|t-igi $1}} ddaw-agi}}.",
        "category-file-count-limited": "{{PLURAL:$1|Afaylu agi yella|$1 ifuyla agi llan}} deg taggayt agi.",
        "rcfilters-invalid-filter": "Yir imzizdig",
        "rcfilters-filterlist-title": "Imzizdigen",
        "rcfilters-filterlist-whatsthis": "Amek iteddu?",
+       "rcfilters-filterlist-feedbacklink": "Ini-yaɣ-d tiktiwin-ik ɣef ifecka n uzizdeg (imaynuten)",
        "rcfilters-highlightbutton-title": "Sebṛureq igmaḍ",
        "rcfilters-highlightmenu-title": "Fren ini",
        "rcfilters-highlightmenu-help": "Fren ini n usebṛuṛeq n tmeẓlit-agi",
index b4cd8b7..7e3c57a 100644 (file)
        "rcfilters-empty-filter": "활성화된 필터가 없습니다. 모든 기여가 표시됩니다.",
        "rcfilters-filterlist-title": "필터",
        "rcfilters-filterlist-whatsthis": "이것들이 어떻게 동작합니까?",
-       "rcfilters-filterlist-feedbacklink": "ì\83\88ë¡\9cì\9a´ (ë² í\83\80) í\95\84í\84°에 대한 의견을 주세요",
+       "rcfilters-filterlist-feedbacklink": "ì\9d´ (ì\83\88ë¡\9cì\9a´) í\95\84í\84° ë\8f\84구에 대한 의견을 주세요",
        "rcfilters-highlightbutton-title": "결과 강조",
        "rcfilters-highlightmenu-title": "색 선택",
        "rcfilters-highlightmenu-help": "이 속성을 강조할 색을 선택하십시오",
        "rcfilters-noresults-conflict": "검색 조건이 충돌하기 때문에 결과를 찾을 수 없습니다",
        "rcfilters-state-message-subset": "필터의 결과가 다음의 범위가 더 넓은 {{PLURAL:$2|필터}}의 결과에 포함되기 때문에 이 필터는 효력이 없습니다 (구별을 위해 강조해 보십시오): $1",
        "rcfilters-state-message-fullcoverage": "이 그룹의 모든 필터를 선택하는 것은 아무 것도 선택하지 않는 것과 동일하므로 이 필터는 효력이 없습니다. 그룹은 다음을 포함합니다: $1",
-       "rcfilters-filtergroup-authorship": "원작자 기여",
+       "rcfilters-filtergroup-authorship": "기여의 저자",
        "rcfilters-filter-editsbyself-label": "당신의 변경사항",
        "rcfilters-filter-editsbyself-description": "당신의 기여.",
        "rcfilters-filter-editsbyother-label": "다른 사용자의 변경사항",
        "rcfilters-liveupdates-button-title-on": "실시간 업데이트 끄기",
        "rcfilters-liveupdates-button-title-off": "새로운 변경사항이 발생하면 표시",
        "rcfilters-watchlist-markSeen-button": "모든 변경사항을 본 것으로 표시",
+       "rcfilters-watchlist-editWatchlist-button": "내 주시문서 목록 편집",
        "rcnotefrom": "아래는 <strong>$3, $4</strong>부터 시작하는 {{PLURAL:$5|바뀜이 있습니다}}. (최대 <strong>$1</strong>개가 표시됨)",
        "rclistfromreset": "날짜 선택 초기화",
        "rclistfrom": "$3 $2부터 시작하는 새로 바뀐 문서 보기",
        "apisandbox-sending-request": "API 요청을 보내는 중...",
        "apisandbox-loading-results": "API 결과를 받는 중...",
        "apisandbox-results-error": "API 질의 응답을 불러오는 도중 오류 발생: $1.",
-       "apisandbox-results-login-suppressed": "í\95´ë\8b¹ ì\9a\94ì²­ì\9d\80 ë¡\9cê·¸ì\9d¸ì\9d\84 í\95\98ì§\80 ì\95\8aì\9d\80 ì\82¬ì\9a©ì\9e\90ê°\80 ë\8f\99ì\9d¼ ì¶\9cì²\98 ë³´ì\95\88ì\9d\84 ì\9a°í\9a\8cí\95\98기 ì\9c\84í\95´ ì§\84í\96\89ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤. API ì\83\8cë\93\9cë°\95ì\8a¤ì\9d\98 ì\9e\90ë\8f\99 í\86 í\81° ì¡°ì \95ì\9d´ í\95´ë\8b¹ ì\9a\94ì²­ì\97\90 ë\8c\80í\95´ ì \9cë\8c\80ë¡\9c ì\9e\91ë\8f\99í\95\98ì§\80 ì\95\8aë\8a\94ì§\80 í\99\95ì\9d¸í\95\98ê³ , 수동으로 채워주세요.",
+       "apisandbox-results-login-suppressed": "í\95´ë\8b¹ ì\9a\94ì²­ì\9d\80 ë¸\8cë\9d¼ì\9a°ì \80ì\9d\98 ë\8f\99ì\9d¼ ì¶\9cì²\98 ë³´ì\95\88ì\9d\84 ì\9a°í\9a\8cí\95\98기 ì\9c\84í\95´ ì\82¬ì\9a©ë\90  ì\88\98 ì\9e\88기 ë\95\8c문ì\97\90 ë¡\9cê·¸ì\95\84ì\9b\83ë\90\9c ì\82¬ì\9a©ì\9e\90 ì\9e\90격ì\9c¼ë¡\9c ì²\98리ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤. API ì\83\8cë\93\9cë°\95ì\8a¤ì\9d\98 ì\9e\90ë\8f\99 í\86 í\81° ì¡°ì \95ì\9d´ í\95´ë\8b¹ ì\9a\94ì²­ì\97\90 ë\8c\80í\95´ ì \9cë\8c\80ë¡\9c ì\9e\91ë\8f\99í\95\98ì§\80 ì\95\8aì\9c¼ë\8b\88, 수동으로 채워주세요.",
        "apisandbox-request-selectformat-label": "요청한 데이터를 보여주기:",
        "apisandbox-request-format-url-label": "URL 쿼리 문자열",
        "apisandbox-request-url-label": "요청 URL:",
index 810e3ec..749ef09 100644 (file)
        "rcfilters-hours-title": "Rezent Stonnen",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|Dag|Deeg}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|Stonn|Stonnen}}",
+       "rcfilters-highlighted-filters-list": "Ervirgehuewen: $1",
        "rcfilters-quickfilters": "Gespäichert Filteren",
        "rcfilters-quickfilters-placeholder-title": "Nach keng Linke gespäichert",
        "rcfilters-quickfilters-placeholder-description": "Fir Är Filterastellungen z'änneren a méi spéit nees ze benotzen, klickt op d'Zeeche  fir Lieszeechen (bookmark) am Beräich vun den Aktive Filteren hei drënner.",
        "rcfilters-liveupdates-button-title-on": "Live-Aktualiséierungen ausschalten",
        "rcfilters-liveupdates-button-title-off": "Nei Ännerunge weisen esoubal wéi se gemaach ginn",
        "rcfilters-watchlist-markSeen-button": "All Ännerungen als gesi markéieren",
+       "rcfilters-watchlist-editWatchlist-button": "Ännert Är Lëscht vun iwwerwaachte säiten",
        "rcnotefrom": "Hei drënner {{PLURAL:$5|gëtt d'Ännerung|ginn d'Ännerungen}} zanter <strong>$3, $4</strong> (maximal <strong>$1</strong> Ännerunge gi gewisen).",
        "rclistfrom": "Nei Ännerunge vum $3 $2 u weisen",
        "rcshowhideminor": "Kleng Ännerunge $1",
index 0b5e469..f61d797 100644 (file)
        "page_first": "pirm",
        "page_last": "pask",
        "histlegend": "Skirtumai tarp versijų: pažymėkite lyginamas versijas ir spustelkite ''Enter'' klavišą arba mygtuką apačioje.<br />\nŽymėjimai: (dab) = palyginimas su naujausia versija,\n(pask) = palyginimas su prieš tai buvusia versija, S = smulkus keitimas.",
-       "history-fieldset-title": "Ieškoti istorijoje",
+       "history-fieldset-title": "Ieškoti versijų",
        "history-show-deleted": "Tik ištrinti",
        "histfirst": "seniausi",
        "histlast": "paskutiniai",
        "rcfilters-highlightmenu-help": "Pasirinkite spalvą šio elemento paryškinimui",
        "rcfilters-filterlist-noresults": "Nerastas toks filtras",
        "rcfilters-noresults-conflict": "Nerasta jokių rezultatų, nes paieškos kriterijai konfliktuoja",
-       "rcfilters-filtergroup-registration": "Vartotojo registracija",
-       "rcfilters-filter-registered-label": "Registruoti",
-       "rcfilters-filter-registered-description": "Prisijungę redaktoriai.",
-       "rcfilters-filter-unregistered-label": "Neregistruoti",
-       "rcfilters-filter-unregistered-description": "Redaktoriai, kurie nėra prisijungę.",
        "rcfilters-filter-editsbyself-label": "Jūsų keitimai",
        "rcfilters-filter-editsbyself-description": "Jūsų keitimai.",
        "rcfilters-filter-editsbyother-label": "Kitų keitimai",
        "rcfilters-filter-editsbyother-description": "Visi keitimai, išskyrus jūsų.",
        "rcfilters-filtergroup-userExpLevel": "Patirties lygis (tik registruotiems vartotojams)",
+       "rcfilters-filter-user-experience-level-registered-label": "Registruoti",
+       "rcfilters-filter-user-experience-level-registered-description": "Prisijungę redaktoriai.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Neregistruoti",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Redaktoriai, kurie nėra prisijungę.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Naujokai",
        "rcfilters-filter-user-experience-level-newcomer-description": "Mažiau nei 10 keitimų ir 4 dienų aktyvumo.",
        "rcfilters-filter-user-experience-level-learner-label": "Mokiniai",
        "fileduplicatesearch-noresults": "Nėra failo pavadinimu \"$1\".",
        "specialpages": "Specialieji puslapiai",
        "specialpages-note-top": "Paaiškinimai",
-       "specialpages-note": "* Įprasti specialieji puslapiai.\n* <span class=\"mw-specialpagerestricted\">Apriboto pasiekiamumo specialieji puslapiai.</span>",
        "specialpages-group-maintenance": "Sistemos palaikymo pranešimai",
        "specialpages-group-other": "Kiti specialieji puslapiai",
        "specialpages-group-login": "Prisijungti / sukurti paskyrą",
        "compare-invalid-title": "Jūsų nurodytas pavadinimas neleistinas.",
        "compare-title-not-exists": "Pavadinimas, kurį nurodėte, neegzistuoja.",
        "compare-revision-not-exists": "Keitimas, kurį nurodėte, neegzistuoja.",
+       "diff-form": "'''forma'''",
        "dberr-problems": "Atsiprašome! Svetainei iškilo techninių problemų.",
        "dberr-again": "Palaukite kelias minutes ir perkraukite puslapį.",
        "dberr-info": "(Nepavyksta pasiekti duomenų bazės: $1)",
        "logentry-delete-delete_redir": "$1 pervadindamas {{GENDER:$2|ištrynė}} buvusį nukreipimą $3",
        "logentry-delete-restore": "$1 atkūrė puslapį $3 ($4)",
        "logentry-delete-restore-nocount": "$1 atkūrė puslapį $3",
+       "restore-count-revisions": "{{PLURAL:$1|1 versija|versijų: $1}}",
        "restore-count-files": "{{PLURAL:$1|1 failas|$1 failai}}",
        "logentry-delete-event": "$1 {{GENDER:$2|pakeitė}} matomumą {{PLURAL:$5|žurnalo įvykio|$5 žurnalo įvykių}} $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|pakeitė}} matomumą {{PLURAL:$5|versijos|$5 versijų}} puslapyje $3: $4",
index febdab0..c0c6def 100644 (file)
        "right-deletechangetags": "Dzēst [[Special:Tags|iezīmes]] no datubāzes",
        "grant-generic": "\"$1\" tiesību paka",
        "grant-group-page-interaction": "Darboties ar lapām",
+       "grant-group-file-interaction": "Darboties ar multimediju failiem",
        "grant-group-email": "Sūtīt e-pastu",
        "grant-group-high-volume": "Veikt liela apjoma aktivitātes",
        "grant-group-administration": "Veikt administratīvās darbības",
index d49b715..9639f7b 100644 (file)
        "rcfilters-empty-filter": "Нема активни филтри. Прикажани се сите придонеси.",
        "rcfilters-filterlist-title": "Филтри",
        "rcfilters-filterlist-whatsthis": "Како работи ова?",
-       "rcfilters-filterlist-feedbacklink": "Ð\94аÑ\98Ñ\82е Ð¼Ð¸Ñ\81леÑ\9aе Ð·Ð° Ð½Ð¾Ð²Ð¸Ñ\82е (беÑ\82а) Ñ\84илÑ\82Ñ\80и",
+       "rcfilters-filterlist-feedbacklink": "Ð\94аÑ\98Ñ\82е Ð½Ð¸ Ð²Ð°Ñ\88е Ð¼Ð¸Ñ\81леÑ\9aе Ð·Ð° Ð¾Ð²Ð¸Ðµ (нови) Ñ\84илÑ\82еÑ\80Ñ\81ки Ð°Ð»Ð°Ñ\82ки",
        "rcfilters-highlightbutton-title": "Истакнување на исход",
        "rcfilters-highlightmenu-title": "Изберете боја",
        "rcfilters-highlightmenu-help": "Изберете боја за да го истакнете ова својство",
        "rcfilters-liveupdates-button-title-on": "Исклучи поднови во живо",
        "rcfilters-liveupdates-button-title-off": "Прикажува нови промени во живо",
        "rcfilters-watchlist-markSeen-button": "Означи ги сите промени како видени",
+       "rcfilters-watchlist-editWatchlist-button": "Уреди набљудувани",
+       "rcfilters-watchlist-showupdated": "Промени во страниците што ги немате посетено откако се случиле се <strong>задебелени</strong>.",
        "rcnotefrom": "Подолу {{PLURAL:$5|е прикажана промената|се прикажани промените}} почнувајќи од <strong>$3, $4</strong>  (се прикажуваат до <b>$1</b>).",
        "rclistfromreset": "Нов избор на датуми",
        "rclistfrom": "Прикажи нови промени почнувајќи од $3 $2",
index f8e42cd..af1d803 100644 (file)
        "search-nonefound": "La percura nun porduziu resultados.",
        "powersearch-legend": "Percura abançada",
        "powersearch-ns": "Percurar ne ls spácios nominales:",
-       "powersearch-remember": "Lhembrar seleçon pa las pesquisas feturas",
+       "powersearch-remember": "Lhembrar seleçon pa las percuras feturas",
        "preferences": "Perfréncias",
        "mypreferences": "Las mies perfréncias",
        "prefs-edits": "Númaro d'eidiçones:",
        "version-ext-colheader-description": "Çcriçon",
        "version-software-product": "Perduto",
        "version-software-version": "Berson",
+       "redirect-lookup": "Percurar:",
        "fileduplicatesearch-filename": "Nome de l fexeiro",
        "fileduplicatesearch-submit": "Percurar",
        "specialpages": "Páiginas speciales",
index 1a0dc94..8c67a7c 100644 (file)
        "all-logs-page": "အများနှင့်ဆိုင်သောမှတ်တမ်းအားလုံး",
        "alllogstext": "{{SITENAME}}၏ ရရှိနိုင်သော မှတ်တမ်းများအားလုံး ပေါင်းစည်းပြသခြင်း ဖြစ်သည်။\nမှတ်တမ်းအမျိုးအစား၊ အသုံးပြုသူအမည် (စာလုံးအကြီးအသေး)၊ သို့မဟုတ် သက်ဆိုင်ရာ စာမျက်နှာ (စာလုံးအကြီးအသေး) ကို ရွေးချယ်ခြင်းဖြင့် ကြည့်ရှုမှုကို အကျဉ်းချုံးနိုင်ပါသည်။",
        "logempty": "မှတ်တမ်းထဲတွင် ကိုက်ညီသော အရာများ မရှိပါ။",
+       "showhideselectedlogentries": "ရွေးချယ်ထားသော မှတ်တမ်းများ၏ မြင်နိုင်မှုကို ပြောင်းလဲရန်",
        "checkbox-all": "အားလုံး",
        "allpages": "စာမျက်နှာအားလုံး",
        "nextpage": "နောက်ထပ်စာမျက်နှာ ($1)",
        "markedaspatrolledtext": "[[:$1]] ၏ ရွေးချယ်ထားသော တည်းဖြတ်မူကို စောင့်ကြပ်စစ်ဆေးပြီးကြောင်း မှတ်သားပြီးပါပြီ။",
        "markedaspatrollednotify": "$1 သို့ ဤပြောင်းလဲမှုအား စောင့်ကြပ်စစ်ဆေးပြီးကြောင်း မှတ်သားပြီးပါပြီ။",
        "patrol-log-page": "စောင့်ကြပ်စစ်ဆေးမှု မှတ်တမ်း",
+       "log-show-hide-patrol": "စောင့်ကြပ်စစ်ဆေးမှု မှတ်တမ်း $1",
        "filedeleteerror-short": "ဖိုင်ဖျက်ရာတွင် အမှားအယွင်း - $1",
        "previousdiff": "← တည်းဖြတ်မူ အဟောင်း",
        "nextdiff": "ပိုသစ်သော တည်းဖြတ်မှု",
        "logentry-patrol-patrol-auto": "စာမျက်နှာ $3 ၏ တည်းဖြတ်မူ $4 အား $1 က စောင့်ကြပ်စစ်ဆေးပြီးကြောင်း အလိုအလျောက် {{GENDER:$2|မှတ်သားခဲ့သည်}}",
        "logentry-newusers-create": "အသုံးပြုသူအကောင့် $1 ကို {{GENDER:$2|ဖန်တီးခဲ့သည်}}",
        "logentry-newusers-autocreate": "အသုံးပြုသူအကောင့် $1 ကို အလိုအလျောက် {{GENDER:$2|ဖန်တီးခဲ့သည်}}",
+       "logentry-protect-protect": "$1 က  $3 ကို {{GENDER:$2|ကာကွယ်ခဲ့သည်}} $4",
        "logentry-protect-modify": "$3 အတွက် ကာကွယ်မှုအဆင့်ကို $1 {{GENDER:$2|က ပြောင်းလဲခဲ့သည်}} $4",
        "logentry-upload-upload": "$1 သည် $3 ကို {{GENDER:$2|upload တင်ခဲ့သည်}}",
        "logentry-upload-overwrite": "$3 ၏ ဗားရှင်းအသစ်ကို $1 {{GENDER:$2|upload တင်ခဲ့သည်}}",
index 309a992..6ac7a24 100644 (file)
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (se også [[Special:NewPages|liste over nye sider]])",
        "recentchanges-legend-plusminus": "«(±123)»",
        "recentchanges-submit": "Vis",
+       "rcfilters-tag-remove": "Fjern «$1»",
        "rcfilters-legend-heading": "<strong>Liste over forkortelser:</strong>",
        "rcfilters-other-review-tools": "<strong>Andre gjennomgangsverktøy</strong>",
        "rcfilters-group-results-by-page": "Grupper resultater etter side",
        "rcfilters-hours-title": "De siste timene",
        "rcfilters-days-show-days": "{{PLURAL:$1|Én dag|$1 dager}}",
        "rcfilters-days-show-hours": "{{PLURAL:$1|Én time|$1 timer}}",
+       "rcfilters-highlighted-filters-list": "Fremhevet: $1",
        "rcfilters-quickfilters": "Lagrede filtre",
        "rcfilters-quickfilters-placeholder-title": "Ingen lenker lagret enda",
        "rcfilters-quickfilters-placeholder-description": "For å lagre filterinnstillingene og gjenbruk dem senere, klikk på bokmerkeikonet i området Aktive Filtre under.",
index c2722ac..b323961 100644 (file)
        "page_first": "पहिलो",
        "page_last": "अन्तिम",
        "histlegend": "अन्तर चयन:संशोधनहरूको तुलनाको लागि रेडियो बाकसमा क्लिक गरेर इण्टर गर्नुहोस् अथवा तल दिएको बटनमा थिच्नुहोस् <br />\nलिजेंड: (चालू): '''({{int:cur}})''' = अवतरणको बीचमा अन्तर, '''({{int:last}})''' = पहिलाका अवतरणको बीचमा अन्तर, '''{{int:minoreditletter}}''' = सानो परिवर्तन।",
-       "history-fieldset-title": "à¤\87तिहासà¤\95à¥\8b à¤µà¤¿à¤\9aरण à¤\97रà¥\8dनà¥\87",
+       "history-fieldset-title": "सà¤\82शà¥\8bधनà¤\95ा à¤²à¤¾à¤\97ि à¤\96à¥\8bà¤\9cà¥\80 à¤\97रà¥\8dनà¥\81हà¥\8bसà¥\8d",
        "history-show-deleted": "मेटिएका मात्र",
        "histfirst": "पुरानो",
        "histlast": "नयाँ",
        "rcshowhidemine-hide": "लुकाउनुहोस्",
        "rcshowhidecategorization-show": "देखाउनुहोस्",
        "rcshowhidecategorization-hide": "लुकाउनुहोस्",
-       "rclinks": "पछिल्ला $1 परिवर्तनहरू पछिल्ला $2 दिनहरूमा",
+       "rclinks": "पछिल्ला $2 दिनहरूमा भएका पछिल्ला $1 परिवर्तनहरू देखाउनुहोस्",
        "diff": "भिन्न",
        "hist": "इतिहास",
        "hide": "लुकाउनुहोस्",
        "booksources-text": "तल दिइएको सूची नयाँ तथा पूराना किताब बेच्ने लगायत तपाईंले खोज्नु भएको किताबका बारेमा थप जानकारी भएका अन्य साइटका लिंकहरू हुन् ।",
        "booksources-invalid-isbn": "यो आइएसबीएन सहि छैन; मूल स्रोतबाट नक्कल गर्दा भएको त्रुटिको जाँच गर्नुहोस।",
        "specialloguserlabel": "निष्पादक:",
-       "speciallogtitlelabel": "लक्ष्य (शीर्षक वा प्रयोगकर्ता)",
+       "speciallogtitlelabel": "लक्ष्य (शीर्षक वा {{ns:user}}:प्रयोगकर्ताका लागि प्रयोगकर्ता नाम)",
        "log": "लगहरू",
        "logeventslist-submit": "देखाउनुहोस्",
        "all-logs-page": "सबै सार्वजनिक लगहरू",
        "tooltip-feed-rss": "यो पृष्ठको लागि RSS फिड",
        "tooltip-feed-atom": "यो पृष्ठको लागि एटम फिड",
        "tooltip-t-contributions": "{{GENDER:$1|यस प्रयोगकर्ता}}का योगदानहरूको सूची हेर्नुहोस्",
-       "tooltip-t-emailuser": "यो प्रयोगकर्तालाई इमेल पठाउनुहोस्",
+       "tooltip-t-emailuser": "{{GENDER:$1|यस प्रयोगकर्ता}}लाई इमेल पठाउनुहोस्",
        "tooltip-t-info": "यस पृष्ठको बारेमा थप जानकारी",
        "tooltip-t-upload": "फाइल अपलोड गर्ने",
        "tooltip-t-specialpages": "सबै विशेष पृष्ठहरूको सूची",
        "htmlform-cloner-delete": "हटाउने",
        "htmlform-cloner-required": "कम्तिमा एउटामा आवश्यक छ ।",
        "logentry-delete-delete": "$1 द्वारा पृष्ठ $3 {{GENDER:$2|मेटाइयो}}",
-       "logentry-delete-restore": "$3 पृष्ठ $1ले {{GENDER:$2|पुनर्स्थापित}} गरेको हो",
+       "logentry-delete-restore": "$1 {{GENDER:$2|पुनर्स्थापित}} पृष्ठ $3 ($4)",
        "logentry-delete-event": "$1 ले $3 पृष्ठको लग {{PLURAL:$5|प्रविष्टि|प्रविष्टिहरू}}को दृश्यता {{GENDER:$2|परिवर्तन गर्यो}}: $4",
        "logentry-delete-revision": "$1 ले $3 पृष्ठको {{PLURAL:$5|एक अवतरण|$5 अवतरणहरू}}को दृश्यता {{GENDER:$2|परिवर्तन गर्यो}}: $4",
        "logentry-delete-event-legacy": "$1 ले $3 पृष्ठमा लग क्रियाहरूको दृश्यता {{GENDER:$2|परिवर्तन गर्यो}}",
        "special-characters-title-emdash": "इएम ड्यास",
        "special-characters-title-minus": "घटाउने चिन्ह",
        "mw-widgets-titleinput-description-new-page": "हालसम्म पृष्ठ उपलब्ध छैन्",
-       "mw-widgets-titleinput-description-redirect": "$1 मा जाने"
+       "mw-widgets-titleinput-description-redirect": "$1 मा जाने",
+       "randomrootpage": "यादृच्छिक शीर्ष पृष्ठ"
 }
index 3bb07fd..a823223 100644 (file)
        "lineno": "Line $1:",
        "compareselectedversions": "Samanlikn valde versjonar",
        "showhideselectedversions": "Vis/løyn valde versjonar",
-       "editundo": "fjern endringa",
+       "editundo": "fjern",
        "diff-empty": "(Ingen skilnad)",
        "diff-multi-sameuser": "({{PLURAL:$1|Éin mellomversjon|$1 mellomversjonar}} av den same brukaren er ikkje {{PLURAL:$1|vist|viste}})",
        "diff-multi-otherusers": "({{PLURAL:$1|Éin mellomliggjande versjon|$1 mellomliggjande versjonar}} av {{PLURAL:$2|éin annan brukar|$2 brukarar}} er ikkje {{PLURAL:$1|vist|viste}})",
        "prefs-watchlist-days-max": "Høgst {{PLURAL:$1|éin dag|$1 dagar}}",
        "prefs-watchlist-edits": "Talet på endringar som viser i den utvida overvakingslista:",
        "prefs-watchlist-edits-max": "Høgst 1000",
-       "prefs-watchlist-token": "Emne på overvakingslista:",
+       "prefs-watchlist-token": "Nykel for overvakingslista:",
        "prefs-misc": "Andre",
        "prefs-resetpass": "Endra passord",
        "prefs-changeemail": "↓Endre e-postadresse",
        "badsig": "Ugyldig råsignatur, sjekk HTML-kodinga.",
        "badsiglength": "Signaturen din er for lang. Han må vere under {{PLURAL:$1|eitt teikn|$1 teikn}}.",
        "yourgender": "Korleis ynskjer du å skildrast?",
-       "gender-unknown": "Eg ynskjer ikkje å spesifisera",
+       "gender-unknown": "Programvara vil omtala deg med med kjønnsnøytrale ord der relevant og mogeleg",
        "gender-male": "Han endrar wikisider",
        "gender-female": "Ho endrar wikisider",
        "prefs-help-gender": "Det er valfritt å oppgje dette.\nProgramvara brukar denne verdien for å bruka rett grammatisk kjønn når ho rettar seg til deg eller nemner deg for andre.\nDenne informasjonen vil vera offentleg.",
        "rcfilters-view-namespaces-tooltip": "Filtrer resultat etter namnerom",
        "rcfilters-view-tags-tooltip": "Filtrer resultat etter endringsmerke",
        "rcnotefrom": "Nedanfor er endringane gjorde sidan <strong>$2</strong> viste (opp til <strong>$1</strong> stykke)",
+       "rclistfromreset": "Nullstill datoval",
        "rclistfrom": "Vis nye endringar sidan $3 $2",
        "rcshowhideminor": "$1 småplukk",
        "rcshowhideminor-show": "Vis",
index 9c862ad..90b49a3 100644 (file)
        "anontalk": "ଆଲୋଚନା",
        "navigation": "ଦିଗବାରେଣି",
        "and": "&#32;ଓ",
-       "qbfind": "ଖୋଜନ୍ତୁ",
-       "qbbrowse": "ଦେଖିବେ",
-       "qbedit": "ସମ୍ପାଦନା (Edit)",
-       "qbpageoptions": "ଏହି ପୃଷ୍ଠାଟି",
-       "qbmyoptions": "ମୋ ପୃଷ୍ଠାଗୁଡ଼ିକ",
        "faq": "ବାରମ୍ବାର ପଚରାଯାଉଥିବା ପ୍ରଶ୍ନ",
-       "faqpage": "Project:ବାରମ୍ବାର ପଚରାଯାଉଥିବା ପ୍ରଶ୍ନ",
        "actions": "କାର୍ଯ୍ୟକ୍ରମ",
        "namespaces": "ନେମସ୍ପେସ",
        "variants": "ନିଆରା",
        "edit-local": "ସ୍ଥାନୀୟ ବିବରଣ ସମ୍ପାଦନା କରନ୍ତୁ",
        "create": "ତିଆରି କରନ୍ତୁ",
        "create-local": "ନିଜର ସ୍ଥାନୀୟ ବିବରଣ ଯୋଡ଼ନ୍ତୁ",
-       "editthispage": "ଏହି ପୃଷ୍ଠାଟିକୁ ବଦଳାଇବେ",
-       "create-this-page": "ଏହି ପୃଷ୍ଠା ତିଆରି କରିବେ",
        "delete": "ଲିଭାଇବେ",
-       "deletethispage": "ଏହି ପୃଷ୍ଠାଟି ଲିଭାଇବେ",
-       "undeletethispage": "ଏହି ପୃଷ୍ଠାଟିକୁ ଲିଭାଇବେ ନାହିଁ",
        "undelete_short": "{{PLURAL:$1|ଗୋଟିଏ ବଦଳ|$1ଟି ବଦଳ}} ଯାହା ଲିଭାସରିଛି ତାହାକୁ ପଛକୁ ଫେରାଇଦେବା",
        "viewdeleted_short": "{{PLURAL:$1|ଗୋଟିଏ ଲିଭାଯାଇଥିବା ବଦଳ|$1ଟି ଲିଭାଯାଇଥିବା ବଦଳ}} ଦେଖାଇବେ",
        "protect": "କିଳିବେ",
        "protect_change": "ବଦଳାଇବା",
-       "protectthispage": "ଏହି ପୃଷ୍ଠାଟିକୁ କିଳିବେ",
        "unprotect": "ସୁରକ୍ଷା ସ୍ତରକୁ ବଦଳାଇବେ",
-       "unprotectthispage": "ଏହି ପୃଷ୍ଠା ପାଇଁ ସୁରକ୍ଷାର ପ୍ରକାର ବଦଳାଇବେ",
        "newpage": "ନୂଆ ପୃଷ୍ଠା",
-       "talkpage": "ପୃଷ୍ଠାକୁ ଆଲୋଚନା କରନ୍ତୁ",
        "talkpagelinktext": "ଆଲୋଚନା",
        "specialpage": "ବିଶେଷ ପୃଷ୍ଠା",
        "personaltools": "ନିଜର ଟୁଲ",
-       "articlepage": "ସୂଚୀ ପୃଷ୍ଠାଟି ଦେଖାଇବେ",
        "talk": "ଆଲୋଚନା",
        "views": "ଦେଖା",
        "toolbox": "ଉପକରଣ",
        "tool-link-emailuser": "{{GENDER:$1|user}}ଙ୍କୁ ଇ-ମେଲ କରନ୍ତୁ",
-       "userpage": "ବ୍ୟବହାରକାରୀଙ୍କ ପୃଷ୍ଠା ଦେଖନ୍ତୁ",
-       "projectpage": "ପ୍ରକଳ୍ପ ପୃଷ୍ଠାଟି ଦେଖାଇବା",
        "imagepage": "ଫାଇଲ ପୃଷ୍ଠାଗୁଡ଼ିକ ଦେଖନ୍ତୁ",
        "mediawikipage": "ମେସେଜ ପୃଷ୍ଠାଟି ଦେଖାଇବେ",
        "templatepage": "ଛାଞ୍ଚ ପୃଷ୍ଠାଗୁଡ଼ିକ ଦେଖନ୍ତୁ",
        "nonunicodebrowser": "'''ଚେତାବନୀ: ଆପଣଙ୍କ ବ୍ରାଉଜରରେ ଇଉନିକୋଡ଼ ସଚଳ କରାଯାଇନାହିଁ ।'''\nଏକ ୱର୍କାଆରାଉଣ୍ଡ ଏକ ଏହିପରି ଜାଗା ଯାହା ଆପଣଙ୍କୁ ନିରାପଦ ଭାବରେ ପୃଷ୍ଠା ସମ୍ପାଦନ କରିବାରେ ସାହାଯ୍ୟ କରିଥାଏ: ଅଣ-ASCII ଅକ୍ଷରସମୂହ ସମ୍ପାଦନା ଘରେ ହେକ୍ସାଡେସିମାଲ କୋଡ଼ ରୂପେ ଦେଖାଯିବ ।",
        "editingold": "'''ଚେତାବନୀ: ଆପଣ ଏହି ପୃଷ୍ଠାର ଏକ ଅଚଳ ପୁରାତନ ସଙ୍କଳନକୁ ବଦଳାଉଛନ୍ତି ।'''\nଯଦି ଆପଣ ଏହାକୁ ସାଇତିବେ, ନୂଆ ସଙ୍କଳନ ଯାଏଁ କରାଯାଇଥିବା ସବୁ ବଦଳ ନଷ୍ଟ ହୋଇଯିବ ।",
        "yourdiff": "ତଫାତ",
-       "copyrightwarning": "ଦୟାକରି ଜାଣିରଖନ୍ତୁ ଯେ {{SITENAME}}କୁ ସବୁଯାକ ଅବଦାନ $2 ଅଧିନରେ ପ୍ରକାଶ କରାଯିବ । (ଅଧିକ ଜାଣିବା ପାଇଁ $1 ଦେଖନ୍ତୁ)\nଯଦି ଆପଣ ନିଜର ଲେଖା ନିର୍ଦୟ ଭାବେ ସମ୍ପାଦିତ ହେଉ ବୋଲି ଚାହୁଁନାହାନ୍ତି ବା ବଣ୍ଟନ କରାଯାଉ ବୋଲି ଚାହୁଁ ନାହାନ୍ତି ତେବେ ତାହା ଏଠାରେ ଦିଅନ୍ତୁ ନାହିଁ ।<br />\nଆପଣ ଆମପକ୍ଷେ ମଧ୍ୟ ପ୍ରତିଜ୍ଞା କରୁଛନ୍ତି ଯେ ଏହା ଆପଣ ନିଜେ ଲେଖିଛନ୍ତି, କିମ୍ବା ଏକ ପବ୍ଲିକ ଡୋମେନରୁ ବା ମାଗଣା ଓ ଖୋଲା ଲାଇସେନ୍ସ ଥିବା ସାଇଟରୁ ନକଲ କରି ଆଣିଛନ୍ତି ।\n'''ଅନୁମତି ବିନା ସତ୍ଵାଧିକାର ଥିବା କାମ ଏଠାରେ ଦିଅନ୍ତୁ ନାହିଁ !'''",
+       "copyrightwarning": "ଦà­\9fାà¬\95ରି à¬\9cାଣିରà¬\96ନà­\8dତà­\81 à¬¯à­\87 {{SITENAME}}à¬\95à­\81 à¬¸à¬¬à­\81ଯାà¬\95 à¬\85ବଦାନ $2 à¬\85ଧିନରà­\87 à¬ªà­\8dରà¬\95ାଶ à¬\95ରାଯିବ à¥¤ (à¬\85ଧିà¬\95 à¬\9cାଣିବା à¬ªà¬¾à¬\87à¬\81 $1 à¬¦à­\87à¬\96ନà­\8dତà­\81)\nଯଦି à¬\86ପଣ à¬¨à¬¿à¬\9cର à¬²à­\87à¬\96ା à¬¨à¬¿à¬°à­\8dଦà­\9f à¬­à¬¾à¬¬à­\87 à¬¸à¬®à­\8dପାଦିତ à¬¹à­\87à¬\89 à¬¬à­\8bଲି à¬\9aାହà­\81à¬\81ନାହାନà­\8dତି à¬¬à¬¾ à¬¬à¬£à­\8dà¬\9fନ à¬\95ରାଯାà¬\89 à¬¬à­\8bଲି à¬\9aାହà­\81à¬\81 à¬¨à¬¾à¬¹à¬¾à¬¨à­\8dତି à¬¤à­\87ବà­\87 à¬¤à¬¾à¬¹à¬¾ à¬\8fଠାରà­\87 à¬¦à¬¿à¬\85ନà­\8dତà­\81 à¬¨à¬¾à¬¹à¬¿à¬\81 à¥¤<br />\nà¬\86ପଣ à¬\86ମପà¬\95à­\8dଷà­\87 à¬®à¬§à­\8dà­\9f à¬ªà­\8dରତିà¬\9cà­\8dà¬\9eା à¬\95ରà­\81à¬\9bନà­\8dତି à¬¯à­\87 à¬\8fହା à¬\86ପଣ à¬¨à¬¿à¬\9cà­\87 à¬²à­\87à¬\96ିà¬\9bନà­\8dତି, à¬\95ିମà­\8dବା à¬\8fà¬\95 à¬ªà¬¬à­\8dଲିà¬\95 à¬¡à­\8bମà­\87ନରà­\81 à¬¬à¬¾ à¬®à¬¾à¬\97ଣା à¬\93 à¬\96à­\8bଲା à¬²à¬¾à¬\87ସà­\87ନà­\8dସ à¬¥à¬¿à¬¬à¬¾ à¬¸à¬¾à¬\87à¬\9fରà­\81 à¬¨à¬\95ଲ à¬\95ରି à¬\86ଣିà¬\9bନà­\8dତି à¥¤\n'''à¬\85ନà­\81ମତି à¬¬à¬¿à¬¨à¬¾ à¬¸à­\8dଵତà­\8dତà­\8dଵାଧିà¬\95ାର à¬¥à¬¿à¬¬à¬¾ à¬\95ାମ à¬\8fଠାରà­\87 à¬¦à¬¿à¬\85ନà­\8dତà­\81 à¬¨à¬¾à¬¹à¬¿à¬\81 !'''",
        "copyrightwarning2": "ଦୟାକରି ଜାଣିରଖନ୍ତୁ ଯେ {{SITENAME}} ସବୁଯାକ ଅବଦାନ ସମ୍ପାଦିତ ହୋଇପାରିବ, ବଦଳାଯାଇପାରିବ କିମ୍ବା ବାକି ଅବଦାନକାରୀଙ୍କ ଦେଇ କଢ଼ାଯାଇପାରିବ ।\nଯଦି ଆପଣ ନିଜର ଲେଖା ନିର୍ଦୟ ଭାବେ ସମ୍ପାଦିତ ହେଉ ବୋଲି ଚାହୁଁନାହାନ୍ତି ବା ବଣ୍ଟନ କରାଯାଉ ବୋଲି ଚାହୁଁ ନାହାନ୍ତି ତେବେ ତାହା ଏଠାରେ ଦିଅନ୍ତୁ ନାହିଁ ।<br />\nଆପଣ ଆମପକ୍ଷେ ମଧ୍ୟ ପ୍ରତିଜ୍ଞା କରୁଛନ୍ତି ଯେ ଏହା ଆପଣ ନିଜେ ଲେଖିଛନ୍ତି, କିମ୍ବା ଏକ ପବ୍ଲିକ ଡୋମେନରୁ ବା ମାଗଣା ଓ ଖୋଲା ଲାଇସେନ୍ସ ଥିବା ସାଇଟରୁ ନକଲ କରି ଆଣିଛନ୍ତି । (ଦୟାକରି ସବିଶେଷ ପାଇଁ $1 ଦେଖନ୍ତୁ) ।\n'''ଅନୁମତି ବିନା ସତ୍ଵାଧିକାର ଥିବା କାମ ଏଠାରେ ଦିଅନ୍ତୁ ନାହିଁ !'''",
        "longpageerror": "'''ଭୁଲ: ଆପଣ ଦେଇଥିବା ଲେଖାଟି {{PLURAL:$1|କିଲୋବାଇଟ|$1 କିଲୋବାଇଟ}} ଲମ୍ବା, ଯାହାକି ସବୁଠାରୁ ଅଧିକ {{PLURAL:$2|କିଲୋବାଇଟ|$2 କିଲୋବାଇଟ}} ଠାରୁ ବି ଅଧିକ ।'''\nଏହା ସାଇତାଯାଇପାରିବ ନାହିଁ ।",
        "readonlywarning": "ସୂଚନା: ଏହି ଡାଟାବେସଟି ରକ୍ଷଣାବେକ୍ଷଣା ପାଇଁ କିଳାଯାଇଛି । ତେଣୁ ଆପଣ ଆପଣା ସମ୍ପାଦନା ଏବେ ସାଇତି ପାରିବେ ନାହିଁ ।'''\nଆପଣ ଲେଖାସବୁ ଏକ ଟେକ୍ସଟ ଫାଇଲରେ ନକଲ ଏବଂ ପେଷ୍ଟ କରି ଆଗକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ସାଇତି ପାରିବେ ।\n\nଏହାକୁ କିଳିଥିବା ପରିଛା ଏହି କଇଫତ ଦେଇଛନ୍ତି: $1",
        "exif-compression-2": "CCITT ଗୋଠ ୩ ୧-ବିମିୟ ବଦଳାଯାଇଥିବା ହଫମାନ ରନ ଲମ୍ବ ଏନକୋଡ଼ିଙ୍ଗ",
        "exif-compression-3": "CCITT ଗୋଠ ୩ ଫାକ୍ସ ଏନକୋଡ଼ିଙ୍ଗ",
        "exif-compression-4": "CCITT ଗୋଠ ୪ ଫାକ୍ସ ଏନକୋଡ଼ିଙ୍ଗ",
-       "exif-copyrighted-true": "ସତ୍ଵାଧିକାର ଥିବା",
+       "exif-copyrighted-true": "ସà­\8dଵତà­\8dତà­\8dଵାଧିà¬\95ାର à¬¥à¬¿à¬¬à¬¾",
        "exif-copyrighted-false": "କପିରାଇଟ ତଥ୍ୟ ଦିଆଯାଇନାହିଁ",
        "exif-unknowndate": "ଅଜଣା ତାରିଖ",
        "exif-orientation-1": "ସାଧାରଣ",
        "fileduplicatesearch-noresults": "\"$1\" ନାମରେ ଗୋଟିଏ ବି ଫାଇଲ ମିଳିଲା ନାହିଁ ।",
        "specialpages": "ବିଶେଷ ପୃଷ୍ଠା",
        "specialpages-note-top": "ଲିଜେଣ୍ଡ",
-       "specialpages-note": "* ସାଧାରଣ ବିଶେଷ ପୃଷ୍ଠାମାନ ।\n* <span class=\"mw-specialpagerestricted\">କିଳାଯାଇଥିବା ବିଶେଷ ପୃଷ୍ଠାମାନ ।</span>",
        "specialpages-group-maintenance": "ରକ୍ଷଣାବେକ୍ଷଣା ବିବରଣୀ",
        "specialpages-group-other": "ବାକି ବିଶେଷ ପୃଷ୍ଠା",
        "specialpages-group-login": "ଲଗ-ଇନ (Log in)/ ନୂଆ ଖାତା ଖୋଲିବେ (Sign up)",
index 844971b..3480433 100644 (file)
        "rcfilters-empty-filter": "Nenhum filtro ativo. Todas as contribuições são mostradas.",
        "rcfilters-filterlist-title": "Filtros",
        "rcfilters-filterlist-whatsthis": "Como funcionam estes?",
-       "rcfilters-filterlist-feedbacklink": "Forneça feedback sobre os novos filtros (beta)",
+       "rcfilters-filterlist-feedbacklink": "Diga-nos o que você pensa sobre estas (novas) ferramentas de filtragem",
        "rcfilters-highlightbutton-title": "Realçar os resultados",
        "rcfilters-highlightmenu-title": "Selecione uma cor",
        "rcfilters-highlightmenu-help": "Selecione uma cor para realçar esta propriedade",
        "rcfilters-liveupdates-button-title-on": "Desativar as atualizações ao vivo",
        "rcfilters-liveupdates-button-title-off": "Exibir novas mudanças à medida que elas acontecem",
        "rcfilters-watchlist-markSeen-button": "Marque todas as mudanças como visto",
+       "rcfilters-watchlist-editWatchlist-button": "Edite sua lista de páginas vigiadas",
+       "rcfilters-watchlist-showupdated": "As alterações nas páginas que você não visitou desde as mudanças ocorridas estão em <strong>negrito</strong>, com marcadores sólidos.",
        "rcnotefrom": "Abaixo {{PLURAL:$5|é a mudança|são as mudanças}} desde <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Redefinir seleção da data",
        "rclistfrom": "Mostrar as novas alterações a partir das $2 de $3",
index d8cad9a..7f9026d 100644 (file)
@@ -73,7 +73,8 @@
                        "Gato Preto",
                        "Jdforrester",
                        "Mansil",
-                       "Ngl2016"
+                       "Ngl2016",
+                       "RadiX"
                ]
        },
        "tog-underline": "Sublinhar ligações:",
        "prefs-tabs-navigation-hint": "Dica: Pode usar as setas direita e esquerda do teclado para navegar entre os separadores.",
        "userrights": "Privilégios de utilizador",
        "userrights-lookup-user": "Selecionar um utilizador",
-       "userrights-user-editname": "Introduza um nome de utilizador(a):",
+       "userrights-user-editname": "Forneça um nome de utilizador(a):",
        "editusergroup": "Carregar grupos do utilizador",
        "editinguser": "A modificar os privilégios {{GENDER:$1|do utilizador|da utilizadora|do(a) utilizador(a)}}  <strong>[[User:$1|$1]]</strong> $2",
        "viewinguserrights": "A ver os privilégios {{GENDER:$1|do utilizador|da utilizadora}} <strong>[[User:$1|$1]]</strong> $2",
index 036a941..c30ac2d 100644 (file)
        "rcfilters-limit-shownum": "Title for the button that opens the operation to control how many results are shown. \n\nParameters: $1 - Number of results shown",
        "rcfilters-days-title": "Title for the options to change the number of days for the results shown.",
        "rcfilters-hours-title": "Title for the options to change the number of hours for the results shown.",
-       "rcfilters-days-show-days": "Title for the button that opens the operation to control the day range for the results. \n\nParameters: $1 - Number of days shown",
-       "rcfilters-days-show-hours": "Title for the button that opens the operation to control the hour range for the results. \n\nParameters: $1 - Number of hours shown",
+       "rcfilters-days-show-days": "Title for the button that opens the operation to control the day range for the results. \n\nParameters: $1 - Number of days shown\n{{Identical|Day}}",
+       "rcfilters-days-show-hours": "Title for the button that opens the operation to control the hour range for the results. \n\nParameters: $1 - Number of hours shown\n{{Identical|Hour}}",
        "rcfilters-highlighted-filters-list": "Text for the tooltip that is displayed over highlighted results, specifying which filters the result matches in [[Special:RecentChanges]] when RCFilters are enabled. \n\nParameters: $1 - A comma separated list of matching filter names.",
        "rcfilters-quickfilters": "Label for the button that opens the saved filter settings menu in [[Special:RecentChanges]]",
        "rcfilters-quickfilters-placeholder-title": "Title for the text shown in the quick filters menu on [[Special:RecentChanges]] if the user has not saved any quick filters.",
        "rcfilters-liveupdates-button": "Label for the button to enable or disable live updates on [[Special:RecentChanges]]",
        "rcfilters-liveupdates-button-title-on": "Title for the button to enable or disable live updates on [[Special:RecentChanges]] when the feature is ON.",
        "rcfilters-liveupdates-button-title-off": "Title for the button to enable or disable live updates on [[Special:RecentChanges]] when the feature is OFF.",
-       "rcfilters-watchlist-markSeen-button": "Label for the button to mark all changes as seen on [[Special:Watchlist]] when using the structured filters interface.",
+       "rcfilters-watchlist-markseen-button": "Label for the button to mark all changes as seen on [[Special:Watchlist]] when using the structured filters interface.",
+       "rcfilters-watchlist-edit-watchlist-button": "Label for the button to edit the watched pages on [[Special:Watchlist]] when using the structured filters interface.\n\nCf. {{msg-mw|watchlisttools-edit}}.",
+       "rcfilters-watchlist-showupdated": "Message at the top of [[Special:Watchlist]] when the Structured filters are enabled that describes what unseen changes look like.\n\nCf. {{msg-mw|wlheader-showupdated}}",
        "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL",
        "rclistfromreset": "Used on [[Special:RecentChanges]] to reset a selection of a certain date range.",
        "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.",
index 83e492a..3b85640 100644 (file)
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (см. также [[Special:NewPages|список новых страниц]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Показать",
+       "rcfilters-tag-remove": "Удалить '$1'",
        "rcfilters-legend-heading": "<strong>Список сокращений:</strong>",
        "rcfilters-other-review-tools": "<strong>Другие инструменты проверки</strong>",
        "rcfilters-group-results-by-page": "Группировать результаты по странице",
        "rcfilters-hours-title": "Последние часы",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|день|дня|дней}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|час|часа|часов}}",
+       "rcfilters-highlighted-filters-list": "Подсвечено: $1",
        "rcfilters-quickfilters": "Сохранённые фильтры",
        "rcfilters-quickfilters-placeholder-title": "Сохраненных ссылок еще нет",
        "rcfilters-quickfilters-placeholder-description": "Чтобы сохранить настройки фильтра и повторно использовать их позже, щелкните значок закладки в области «Активный фильтр» ниже.",
index fca874c..decb939 100644 (file)
        "mypage": "ورقہ",
        "mytalk": "ڳالھ مہاڑ",
        "anontalk": "ڳالھ مہاڑ",
-       "navigation": "رہنمائی",
+       "navigation": "نیوی ڳیشݨ",
        "and": "&#32;تے",
        "faq": "عام طور تے پچھے ونڄݨ آلے سوال",
        "actions": "کم",
        "namespaces": "ناں جاہیں",
        "variants": "قسماں",
-       "navigation-heading": "Ù\81Û\81رست Ø±Û\81Ù\86Ù\85ائÛ\8c",
+       "navigation-heading": "Ù\86Û\8cÙ\88Û\8c Ú³Û\8cشݨ Ù\81Û\81رست",
        "errorpagetitle": "نقص",
        "returnto": "واپس $1 چلو",
        "tagline": " {{SITENAME}} توں",
        "lastmodifiedat": "ایہ ورقہ چھیکڑی واری  $1 کوں $2 تے تبدیل تھیا ہائی۔",
        "protectedpage": "آم شام ورقہ",
        "jumpto": "ٹپ مارو",
-       "jumptonavigation": "رہنمائی",
+       "jumptonavigation": "نیوی ڳیشݨ",
        "jumptosearch": "ڳولو",
        "pool-errorunknown": "نامعلوم غلطی",
        "poolcounter-usage-error": "استعمال وچ خامی: $1",
        "viewsource": "ماخذ ݙیکھو",
        "viewsource-title": "$1 دا مسودہ ݙیکھو",
        "viewsourcetext": "تساں ایں ورقے کوں صرف ݙیکھ تے ماخز نقل کر سڳدے ہو لیکن تبدیلی نہوے کر سڳدے",
+       "ns-specialprotected": "خاص ورقیاں وچ تبدیلی نی تھی سڳدی",
        "exception-nologin": "لاگ ان نہوے تھئے",
+       "virus-unknownscanner": "ان ڄاݨ اینٹی وائرس",
        "cannotlogoutnow-title": "ہݨ لاگ ان نہوے تھی سڳدے",
        "welcomeuser": "خوش آمدید، $1!",
        "yourname": "صارف دا ناں",
        "oldpassword": "پراݨا پاس ورڈ",
        "newpassword": "نواں پاس ورڈ",
        "retypenew": "نواں پاس ورڈ ولدا لکھو",
+       "resetpass_submit": "پاس ورڈ بݨاؤ تے لاگ ان تھیوو",
+       "changepassword-success": "تہاݙا پاس ورڈ تبدیل تھی ڳیا!",
        "botpasswords": "بوٹ پاس ورڈ",
        "botpasswords-label-appid": "بوٹ ناں:",
        "botpasswords-label-create": "بݨاؤ",
        "rcfilters-filter-minor-label": "معمولی تبدیلیاں",
        "rcfilters-filtergroup-lastRevision": "موجودہ حالت",
        "rcfilters-filter-lastrevision-label": "موجودہ حالت",
-       "rclistfrom": "$3 $2 توں ہونے آلیاں نویاں تبدیلیاں ݙکھاؤ",
+       "rclistfrom": "$3 $2 توں تھیوݨ آلیاں نویاں تبدیلیاں ݙکھاؤ",
        "rcshowhideminor": "$1 معمولی تبدیلیاں",
        "rcshowhideminor-show": "ݙیکھاؤ",
        "rcshowhideminor-hide": "لُکاؤ",
index e99c7ad..beaefe4 100644 (file)
        "rcfilters-empty-filter": "Ni dejavnih filtrov. Prikazani so vsi prispevki.",
        "rcfilters-filterlist-title": "Filtri",
        "rcfilters-filterlist-whatsthis": "Kako to deluje?",
-       "rcfilters-filterlist-feedbacklink": "Podajte povratne informacije o novih (preizkusnih) filtrih",
+       "rcfilters-filterlist-feedbacklink": "Povejte nam, kaj menite o teh (novih) orodjih za filtriranje",
        "rcfilters-highlightbutton-title": "Označi rezultate",
        "rcfilters-highlightmenu-title": "Izberite barvo",
        "rcfilters-highlightmenu-help": "Izberite barvo za označitev te lastnosti",
        "rcfilters-liveupdates-button-title-on": "Izklopi posodobitve v živo",
        "rcfilters-liveupdates-button-title-off": "Prikaži nove spremembe, ko se zgodijo",
        "rcfilters-watchlist-markSeen-button": "Označi vse spremembe kot pregledane",
+       "rcfilters-watchlist-editWatchlist-button": "Uredite svoj seznam nadzorovanih strani",
+       "rcfilters-watchlist-showupdated": "Spremembe strani, ki jih niste obiskali od zadnje spremembe, so prikazane <strong>krepko</strong>, z močnimi oznakami.",
        "rcnotefrom": "{{PLURAL:$5|Navedena je sprememba|Navedeni sta spremembi|Navedene so spremembe}} od <strong>$3 $4</strong> dalje (prikazujem jih do <strong>$1</strong>).",
        "rclistfromreset": "Ponastavi izbiro datuma",
        "rclistfrom": "Prikaži spremembe od $3 $2 naprej",
index 2593dad..4940ba8 100644 (file)
        "cannotlogin-title": "Пријава није могућа",
        "cannotlogin-text": "Пријава није могућа",
        "cannotloginnow-title": "Пријава тренутно није могућа",
-       "cannotloginnow-text": "Ð\9fÑ\80иÑ\98ава Ð½Ð¸Ñ\98е Ð¼Ð¾Ð³Ñ\83Ñ\9bа ÐºÐ¾Ñ\80иÑ\88Ñ\9bеÑ\9aем $1",
+       "cannotloginnow-text": "Ð\9fÑ\80иÑ\98ава Ð½Ð¸Ñ\98е Ð¼Ð¾Ð³Ñ\83Ñ\9bа ÐºÐ°Ð´Ð° Ñ\81е ÐºÐ¾Ñ\80иÑ\81Ñ\82и $1.",
        "cannotcreateaccount-title": "Отварање налога није могуће",
        "yourdomainname": "Домен:",
        "password-change-forbidden": "Не можете да промените лозинку на овом викију.",
        "botpasswords-label-delete": "Обриши",
        "botpasswords-label-resetpassword": "Ресетуј лозинку",
        "botpasswords-label-grants-column": "Одобрено",
-       "botpasswords-bad-appid": "\"$1\" није валидан назив бота",
+       "botpasswords-bad-appid": "„$1” није валидан назив бота.",
        "botpasswords-insert-failed": "Неуспешно додавање бота \"$1\". Да ли је већ додат?",
+       "botpasswords-update-failed": "Није могуће ажурирати бота \"$1\". Да ли је обрисан?",
        "botpasswords-created-title": "Направљена лозинка бота",
        "botpasswords-created-body": "Лозинка за бота \"$1\" корисника \"$2\" је направљена.",
        "botpasswords-updated-title": "Лозинка бота промењена",
+       "botpasswords-updated-body": "Лозинка за бота \"$1\" корисника \"$2\" је ажурирана.",
        "botpasswords-deleted-title": "Обрисана лозинка бота",
+       "botpasswords-deleted-body": "Лозинка за бота \"$1\" корисника \"$2\" је обрисана.",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider није доступан.",
        "resetpass_forbidden": "Лозинка не може бити промењена",
        "resetpass_forbidden-reason": "Лозинке није могуће променити: $1",
        "resetpass-no-info": "Морате бити пријављени да бисте приступили овој страници.",
        "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=baz}}</nowiki></code>.",
        "grant-group-file-interaction": "Уређивање датотека",
        "grant-group-watchlist-interaction": "Уређивање вашег списка надгледања",
        "grant-group-email": "Пошаљи имејл",
+       "grant-group-other": "Разне активности",
        "grant-blockusers": "Блокирање и деблокирање корисника",
        "grant-createaccount": "Отварање налога",
        "grant-createeditmovepage": "Прављење, уређивање и премештање страница",
        "grant-editprotected": "Уређивање заштићених страница",
        "grant-highvolume": "Масовно уређивање",
        "grant-patrol": "Патролирање измена",
+       "grant-privateinfo": "Приступи приватним информацијама",
        "grant-protect": "Закључавање и откључавање страница",
        "grant-rollback": "Враћање измена",
        "grant-sendemail": "Слање имејлова другим корисницима",
        "action-userrights-interwiki": "уређивање корисничких права на другим викијима",
        "action-siteadmin": "закључавање или откључавање базе података",
        "action-sendemail": "слање имејлова",
+       "action-editmyoptions": "уређивање ваших подешавања",
        "action-editmywatchlist": "измену сопственог списак надгледања",
        "action-viewmywatchlist": "преглед вашег списак надгледања",
        "action-viewmyprivateinfo": "прегледање ваших личних података",
        "action-managechangetags": "прављење и (де)активирање ознака",
        "action-applychangetags": "додавање ознака на ваше измене",
        "action-changetags": "додавање и уклањање разних ознака на појединачним изменама и уносима у дневницима",
+       "action-deletechangetags": "Обриши ознаке из базе података",
        "action-purge": "чишћење привремене меморије ове странице",
        "nchanges": "$1 {{PLURAL:$1|измена|измене|измена}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|измена од ваше последње посете}}",
        "enhancedrc-history": "историја",
        "recentchanges": "Скорашње измене",
-       "recentchanges-legend": "Ð\9fоÑ\81Ñ\82авке скорашњих измена",
+       "recentchanges-legend": "Ð\9eпÑ\86иÑ\98е скорашњих измена",
        "recentchanges-summary": "Пратите скорашње измене на овој страници.",
        "recentchanges-noresult": "Нема промена у задатом времену за задате критеријуме.",
        "recentchanges-feed-description": "Пратите скорашње измене уз помоћ овог довода.",
        "rcfilters-tag-remove": "Обриши $1",
        "rcfilters-legend-heading": "<strong>Списак скраћеница:</strong>",
        "rcfilters-other-review-tools": "<strong>Остали алати за преглед</strong>",
+       "rcfilters-group-results-by-page": "Групиши резултате по страницама",
        "rcfilters-grouping-title": "Груписање",
        "rcfilters-activefilters": "Активни филтери",
        "rcfilters-advancedfilters": "Напредни филтери",
        "rcfilters-hours-title": "Претходних неколико сати",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|дан|дана}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|сат|сата}}",
+       "rcfilters-highlighted-filters-list": "Истакнуто: $1",
        "rcfilters-quickfilters": "Сачувани филтери",
+       "rcfilters-quickfilters-placeholder-title": "Везе још увек нису упамћене",
        "rcfilters-savedqueries-defaultlabel": "Сачувани филтери",
        "rcfilters-savedqueries-rename": "Преименуј",
        "rcfilters-savedqueries-setdefault": "Постави као подразумевано",
        "rcfilters-savedqueries-unsetdefault": "Уклони као подразумевано",
        "rcfilters-savedqueries-remove": "Уклони",
        "rcfilters-savedqueries-new-name-label": "Име",
+       "rcfilters-savedqueries-new-name-placeholder": "Опиши сврху филтера",
        "rcfilters-savedqueries-apply-label": "Направи филтер",
+       "rcfilters-savedqueries-apply-and-setdefault-label": "Направи подразумевани филтер",
        "rcfilters-savedqueries-cancel-label": "Откажи",
        "rcfilters-savedqueries-add-new-title": "Сачувај тренутне поставке филтера",
        "rcfilters-restore-default-filters": "Враћање подразумеваних филтера",
        "rcfilters-clear-all-filters": "Уклони све филтере",
        "rcfilters-show-new-changes": "Погледајте најновије измене",
        "rcfilters-search-placeholder": "Филтер скорашњих измјена (претражите или почните куцати)",
+       "rcfilters-invalid-filter": "Невалидан филтер",
        "rcfilters-empty-filter": "Нема активних филтера. Сви доприноси су приказани.",
        "rcfilters-filterlist-title": "Филтери",
        "rcfilters-filterlist-whatsthis": "Како ово функционише?",
        "rcfilters-filter-editsbyself-description": "Ваши доприноси.",
        "rcfilters-filter-editsbyother-label": "Измене других",
        "rcfilters-filter-editsbyother-description": "Све измене осим Ваших.",
+       "rcfilters-filtergroup-userExpLevel": "Корисничка регистрација и искуство",
        "rcfilters-filter-user-experience-level-registered-label": "Регистровани",
        "rcfilters-filter-user-experience-level-registered-description": "Пријављени уредници.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Нерегистровани",
        "rcfilters-filter-humans-label": "Човјек (није бот)",
        "rcfilters-filter-patrolled-label": "Патролирано",
        "rcfilters-filter-unpatrolled-label": "Непатролирано",
+       "rcfilters-filtergroup-significance": "Значај",
        "rcfilters-filter-minor-label": "Мање измјене",
+       "rcfilters-filter-major-label": "Значајне промене",
+       "rcfilters-filter-major-description": "Промене означене као значајне.",
        "rcfilters-filtergroup-watchlist": "Странице на списку надгледања",
        "rcfilters-filter-watchlist-watched-label": "На списку надгледања",
        "rcfilters-filter-watchlist-watched-description": "Измене страница на Вашем списку надгледања",
        "rcfilters-filter-lastrevision-description": "Само најновија измена на страници.",
        "rcfilters-filter-previousrevision-label": "Раније измене",
        "rcfilters-filter-previousrevision-description": "Све измене које нису најскорашњије измене странице.",
+       "rcfilters-filter-excluded": "Изостављено",
+       "rcfilters-tag-prefix-namespace-inverted": "<strong>:није</strong> $1",
+       "rcfilters-exclude-button-off": "Изостави означено",
        "rcfilters-view-advanced-filters-label": "Напредни филтери",
        "rcfilters-view-namespaces-tooltip": "Филтер резултата према именском простору",
-       "rcfilters-view-tags-tooltip": "Филтер резултата према ознаци измјене",
+       "rcfilters-view-tags-tooltip": "Филтер резултата према ознаци измене",
        "rcnotefrom": "Испод {{PLURAL:$5|је измена|су измене}} од <strong>$3, $4</strong> (до <strong>$1</strong> приказано).",
        "rclistfromreset": "Ресетуј одабир датума",
        "rclistfrom": "Прикажи нове измене почев од $2 $3",
index 8f103a9..64a7353 100644 (file)
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|izmena od vaše poslednje posete}}",
        "enhancedrc-history": "istorija",
        "recentchanges": "Skorašnje izmene",
-       "recentchanges-legend": "Postavke skorašnjih izmena",
+       "recentchanges-legend": "Opcije skorašnjih izmena",
        "recentchanges-summary": "Pratite skorašnje izmene na ovoj stranici.",
        "recentchanges-noresult": "Nema promena u zadatom vremenu za zadate kriterijume.",
        "recentchanges-feed-description": "Pratite skorašnje izmene uz pomoć ovog dovoda.",
index 549fde9..e5587c4 100644 (file)
        "cannotloginnow-title": "Teu bisa asup log ayeuna",
        "cannotloginnow-text": "Kaluar log teu mungkin bisa nalika keur ngagunakeun $1.",
        "cannotcreateaccount-title": "Teu bisa nyieun akun",
+       "cannotcreateaccount-text": "Nangtukeun akun langsung teu aktif di ieu wiki.",
        "yourdomainname": "Domain anjeun",
        "password-change-forbidden": "Anjeun teu bisa ngarobah kecap sandi dina ieu wiki.",
        "externaldberror": "Aya kasalahan dina pangkalan data oténtikasi luar, atawa anjeun mémang teu diwenangkeun pikeun ngaropéa akun luar anjeun.",
        "userlogin-resetpassword-link": "Poho kecap sandi?",
        "userlogin-helplink2": "Pitulung asup log",
        "userlogin-loggedin": "Anjeun geus asup log salaku {{GENDER:$1|$1}}.\nPaké pormulir di handap pikeun asup log salaku pamaké séjén.",
+       "userlogin-reauth": "Anjeun kudu asup log deui pikeun mariksa yén anjeun téh {{GENDER:$1|$1}}",
        "userlogin-createanother": "Jieun akun séjén",
        "createacct-emailrequired": "Alamat surélék:",
        "createacct-emailoptional": "Alamat surélék (teu wajib)",
        "createacct-email-ph": "Asupkeun alamat surélék anjeun",
        "createacct-another-email-ph": "Asupkeun alamat surélék",
        "createaccountmail": "Paké kecap sandi acak sarta kirim ka alamat surélék",
+       "createaccountmail-help": "Bisa dipaké pikeun nyieun akun jang batur tanpa kudu nyaho kecap sandina.",
        "createacct-realname": "Ngaran asli (teu wajib)",
        "createacct-reason": "Alesan",
        "createacct-reason-ph": "Naha bet nyieun akun séjén",
        "nocookiesnew": "Akun pamaké geus dijieun, tapi anjeun can asup log. {{SITENAME}} maké ''cookies'' pikeun ngalog pamaké. Anjeun boga ''cookies'' nu ditumpurkeun. Mangga fungsikeun, teras asup log migunakeun sandiasma sarta kecap sandi anu anyar.",
        "nocookieslogin": "{{SITENAME}} migunakeun ''cookies'' pikeun ngasupkeun pamaké kana log. Anjeun boga ''cookies'' nu ditumpurkeun. Mangga pungsikeun sarta cobian deui.",
        "nocookiesfornew": "Akun pamaké teu bisa dijieun, kusabab sumberna teu bisa dipastikeun.\nPariksa kukisna bisi tumpur, terus cobaan muat ulang ieu kaca.",
+       "createacct-loginerror": "Akun anjeun geu dijieun, ngan anjeun teu bisa langsung asup log sacara otomatis. Pék tuluykeun kana [[Special:UserLogin|asup log manual]].",
        "noname": "Anjeun teu nuliskeun ngaran pamaké nu sah.",
        "loginsuccesstitle": "Geus asup log",
        "loginsuccess": "Anjeun ayeuna geus asup log ka {{SITENAME}} salaku \"$1\".",
        "wrongpasswordempty": "Sandina can kaeusian. Cobaan deui!",
        "passwordtooshort": "Sandina kudu diwangun ku sahanteuna {{PLURAL:$1|1 karakter|$1 karakter}}.",
        "passwordtoolong": "Kecap sandi teu bisa leuwih ti {{PLURAL:$1|1 karakter|$1 karakter}}.",
+       "passwordtoopopular": "Kecap sandi nu guyub teu bisa dipaké. Mangga pilih kecap sandi nu béda.",
        "password-name-match": "Kecap sandi anjeun kudu béda ti sandiasma.",
        "password-login-forbidden": "Sandiasma jeung sandina teu bisa dipaké.",
        "mailmypassword": "Setél ulang kecap sandi",
        "changepassword-success": "Kecap sandi Anjeun geus laksana dirobah!",
        "changepassword-throttled": "Anjeun geus loba teuing nyobaan asup log.\nTungguan $1 méméh nyobaan deui.",
        "botpasswords": "Kecap sandi bot",
+       "botpasswords-summary": "<em>Kecap sandi bot</em> ngamumkinkeun aksés kana akun pamaké ngagunakeun API tanpa maké krédensial asup log utama éta akun. Hak pamaké nu nyangkaruk nalika asup log kalawn kecap sandi bot meureun bakal diwatesan.\n\nUpama anjeun teu apal kunaon anjeun mikahayang ieu lalampahan, alusna mah ulah dilakonan. Saenyana euweuh jalma lian nu digaékeun pikeun nyieun sarta nyumerahkeun kecap sandi ieu bot ka manéhna.",
        "botpasswords-disabled": "Kecap sandi bot dipareuman.",
        "botpasswords-no-central-id": "Pikeun migunakeun kecap sandi bot, anjeun kudu asup log ka akun museur heula.",
        "botpasswords-existing": "Kecap sandi bot sayaga",
        "botpasswords-label-grants": "Aksés nu dibikeun:",
        "botpasswords-label-grants-column": "Diwidian",
        "botpasswords-bad-appid": "Ngaran bot \"$1\" teu sah.",
+       "botpasswords-insert-failed": "Gagal nambahkeun ngaran bot \"$1\". Éta geus pernah ditambahkeun sugah?",
        "botpasswords-update-failed": "Gagal nganyarkeun ngaran bot \"$1\". Baheula pernah dipupus kitu?",
        "botpasswords-created-title": "Kecap sandi bot dijieun",
        "botpasswords-created-body": "Kecap sandi pikeun ngaran bot \"$1\" ti pamaké \"$2\" geus dijieun.",
        "botpasswords-updated-body": "Kecap sandi pikeun ngaran bot \"$1\" ti pamaké \"$2\" geus dianyarkeun.",
        "botpasswords-deleted-title": "Kecap sandi bot dihapus",
        "botpasswords-deleted-body": "Kecap sandi pikeun ngaran bot \"$1\" ti pamaké \"$2\" geus dipupus.",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider teu sayaga.",
+       "botpasswords-restriction-failed": "Wates kecap sandi ngahalangan ieu asup log.",
+       "botpasswords-invalid-name": "Ngaran pamaké nu dibikeun teu ngandung pamisah kecap sandi bot (\"$1\").",
+       "botpasswords-not-exist": "Pamaké \"$1\" teu miboga kecap sandi bot nu ngaranna \"$2\".",
        "resetpass_forbidden": "Sandi henteu bisa dirobah",
        "resetpass_forbidden-reason": "Kecap sandi teu bisa diganti: $1",
        "resetpass-no-info": "Anjeun kudu asup log pkeun bisa muka ieu kaca sacara langsung.",
        "passwordreset-emailtext-user": "Pamaké $1 di {{SITENAME}} ménta nyetél ulang sandi anjeun di {{SITENAME}} ($4). {{PLURAL:$3|Akun}} di handap tumali jeung alamat surélék ieu:\n\n$2\n\n{{PLURAL:$3|Ieu sandi saheulaanan}} bakal kadaluwarsa dina témpo {{PLURAL:$5|sapoé|$5 poé}}.\nAnjeun kudu asup sarta milih sandi anyar ayeuna. Lamun henteu rumasa nyieun ieu pamundut atawa lamun geus inget sandi asli sarta moal ngarobah deui, ieu talatah teu kudu dipaliré.",
        "passwordreset-emailelement": "Sandiasma: \n$1\n\nSandi saheulaanan: \n$2",
        "passwordreset-emailsentemail": "Mun ieu alamat surélék patalian jeung akun anjeun, mangka surélék pikeun nyetél ulang kecap sandi bakal dikirim.",
+       "passwordreset-emailsentusername": "Mun ieu alamat surélék patalian jeung akun anjeun, mangka surélék pikeun nyetél ulang kecap sandi bakal dikirim.",
        "passwordreset-nocaller": "Panggero kudu dibikeun",
        "passwordreset-nosuchcaller": "Panggero can aya: $1",
+       "passwordreset-ignored": "Pamulangan kecap sandi teu kasiwer. Sugan panyadia can diatur?",
        "passwordreset-invalidemail": "Alamat surélék teu sah",
        "passwordreset-nodata": "Boh ngaran pamaké, boh alamat surélék teu dibéré",
        "changeemail": "Ganti atawa pupus alamat surélék",
        "readonlywarning": "<strong>Awas: pangkalan data dikonci pikeun diropéa, anjeun moal bisa nyimpen éditan anjeun ayeuna.</strong>\nMun perlu, simpen heula téksna kana berkas téks pikeun diasupkeun deui séjén mangsa.\n\nSistim kuncén anu ngonci pangkalan data méré katerangan: $1",
        "protectedpagewarning": "'''AWAS: ieu kaca dikonci sarta ngan bisa dirobah ku pamaké nu statusna kuncén.'''\nÉntri log panungtungan ditémbongkeun di handap:",
        "semiprotectedpagewarning": "'''Catetan''': ieu kaca dikonci sarta ukur bisa dirobah ku pamaké nu geus kadaptar.\nÉntri log panungtung dibéréndélkeun di handap:",
-       "cascadeprotectedwarning": "'''Awas''': ieu kaca dikonci sahingga ukur bisa dirobah ku kuncén, sabab kaasup {{PLURAL:$1|kaca|kaca}} dina panyalindungan-ngaruntuy di handap ieu:",
+       "cascadeprotectedwarning": "<strong>Mangkahadé:</strong> Ieu kaca geus ditangtayungan sangkan bisa diédit ku pamaké kalawan [[Special:ListGroupRights|hak aksés tinangtu]] wungkul lantaran ditransklusikeun dina {{PLURAL:$1|kaca|kaca-kaca}} nu ditangtayungan runtuy:",
        "titleprotectedwarning": "'''Awas: ieu kaca dikonci sarta ngan bisa dijieun ku pamaké anu boga [[Special:ListGroupRights|kawenangan husus]].'''\nÉntri log panungtung dibéréndélkeun di handap:",
        "templatesused": "{{PLURAL:$1|Citakan|Citakan}} nu dipaké dina ieu kaca:",
        "templatesusedpreview": "{{PLURAL:$1|Citakan|Citakan}} nu dipaké dina ieu pramidang:",
        "permissionserrorstext": "Anjeung teu boga kawenangan pikeun peta kitu, kalawan {{PLURAL:$1|alesan|alesan}} di handap ieu:",
        "permissionserrorstext-withaction": "Anjeun teu ngabogaan hak keur $2, kusabab {{PLURAL:$1|alesan|alesan}} katut:",
        "recreate-moveddeleted-warn": "'''Awas: anjeun keur nyieun deui kaca nu geus kungsi dihapus.'''\n\nMangga émutan deui perlu/henteuna nyieun deui ieu artikel.\nPikeun leuwih écés, di handap dibéréndélkeun logna:",
-       "moveddeleted-notice": "Ieu kaca geus dihapus.\nPikeun rujukan, logna tiasa ditingal di handap ieu.",
+       "moveddeleted-notice": "Ieu kaca geus dipupus.\nLog pamupusan, panangtayungan, sarta alihan ti éta kaca nyangkaruk di handap ieu minangka rujukan.",
        "log-fulllog": "Tempo log sakabéhna",
        "edit-hook-aborted": "Éditan dibolaykeun tanpa pedaran.",
        "edit-gone-missing": "Kaca teu bisa dianyarkeun,\nsigana kusabab geus dihapus.",
        "content-not-allowed-here": "Eusi \"$1\" teu diijinan di kaca [[$2]]",
        "editwarning-warning": "Ninggalkeun ieu kaca bakal ngaleungitkeun parobahan anu tas dijieun.\nUpama anjeun geus asup log, anjeun bisa numpurkeun ieu talatah dina bagian \"{{int:prefs-editing}}\" préferénsi anjeun.",
        "editpage-invalidcontentmodel-title": "Modél kontén teu dirojong",
+       "editpage-invalidcontentmodel-text": "Modél kontén \"$1\" teu dirojong.",
        "editpage-notsupportedcontentformat-title": "Format eusi teu dirojong",
        "editpage-notsupportedcontentformat-text": "Format eusi $1 teu dirojong ku modél eusi $2.",
        "content-model-wikitext": "wikitéks",
        "template-loop-category": "Kaca kawalan citakan bulak-balik",
        "parser-template-recursion-depth-warning": "Citakan patumpang tindih ngaleuwihan wates($1)",
        "language-converter-depth-warning": "Parobah basa ngaleuwihan wates jerona ($1)",
+       "node-count-exceeded-category": "Kaca di mana itungan-node kalampau",
+       "node-count-exceeded-category-desc": "Ieu kaca ngaleuwihan node maksimum.",
        "node-count-exceeded-warning": "Kaca nu ngaleuwihan jumlah node",
        "expansion-depth-exceeded-category": "Kaca-kaca anu jero ékspansina leuwih ti wates",
        "expansion-depth-exceeded-category-desc": "Kacana ngaleuwihan wates jero ékspansina.",
        "mergehistory-fail-bad-timestamp": "Stémpel waktu teu sah.",
        "mergehistory-fail-invalid-source": "Kaca sumber teu sah.",
        "mergehistory-fail-invalid-dest": "Kaca tujuan teu sah.",
+       "mergehistory-fail-permission": "Izin panggabungan jujutan kaca teu nyumponana.",
+       "mergehistory-fail-self-merge": "Kaca asal jeung tujulna sarua.",
+       "mergehistory-fail-timestamps-overlap": "Révisi asal tumpang tindih atawa leuwih anyar batan révisi tujul.",
        "mergehistory-no-source": "Sumber kaca $1 teu aya.",
        "mergehistory-no-destination": "Kaca nu dituju ($1) teu aya.",
        "mergehistory-invalid-source": "Kaca sumber kudu sohéh judulna.",
        "search-category": "(kategori $1)",
        "search-file-match": "(cocog jeung eusi berkas)",
        "search-suggest": "Meureun maksud Anjeun nyaéta: $1",
-       "search-interwiki-caption": "Proyék sawargi",
+       "search-rewritten": "Némbongkeun hasil pikeun $1. Paluruh lain pikeun $2.",
+       "search-interwiki-caption": "Hasil ti proyék sawargi",
        "search-interwiki-default": "Hasil ti $1:",
        "search-interwiki-more": "(saterusna)",
        "search-interwiki-more-results": "hasil lianna",
        "showingresultsinrange": "Di handap dipidangkeun nepi ka {{PLURAL:$1|<strong>1</strong> hasil}} ti #<strong>$2</strong> nepi ka #<strong>$3</strong>.",
        "search-showingresults": "{{PLURAL:$4|Hasil <strong>$1</strong> ti <strong>$3</strong>|Hasil <strong>$1 - $2</strong> ti <strong>$3</strong>}}",
        "search-nonefound": "Euweuh hasil nu cocog jeung kueri.",
+       "search-nonefound-thiswiki": "Euweuh hasil nu cocog jeung pamundutan di ieu situs.",
        "powersearch-legend": "Panéangan tuluy",
        "powersearch-ns": "Téangan di ngaranspasi:",
        "powersearch-togglelabel": "Pariksa:",
        "powersearch-toggleall": "Kabéh",
        "powersearch-togglenone": "Henteu aya",
+       "powersearch-remember": "Inget pilihan pikeun pamaluruhan satuluyna",
        "search-external": "Panéangan luar",
        "searchdisabled": "Punten! Néangan téks lengkep di {{SITENAME}} kanggo samentawis ditumpurkeun pikeun alesan kinerja. Jalaran kitu, saheulaanan anjeun bisa nyungsi di Google di handap ieu. Catet yén indéxna ngeunaan eusi {{SITENAME}} bisa jadi teu mutahir.",
+       "search-error": "Aya kesalahan nalika maluruh: $1",
+       "search-warning": "Aya panginget nalika maluruh: $1",
        "preferences": "Préferénsi",
        "mypreferences": "Préferénsi",
        "prefs-edits": "Jumlah éditan:",
        "prefs-watchlist": "Awaskeuneun",
        "prefs-editwatchlist": "Ropéa awaskeuneun",
        "prefs-editwatchlist-label": "Ropéa éntri anu aya dina awaskeuneun:",
+       "prefs-editwatchlist-edit": "Tempo jeung pupus judul di daptar awaskeuneun anjeun",
+       "prefs-editwatchlist-raw": "Édit daptar awaskeuneun atah",
        "prefs-editwatchlist-clear": "Bersihan aawaseun anjeun",
        "prefs-watchlist-days": "Jumlah poé anu ditémbongkeun dina daptar awaskeuneun:",
        "prefs-watchlist-days-max": "Panglilana $1 {{PLURAL:$1|poé|poé}}",
-       "prefs-watchlist-edits": "Jumlah parobahan maksimum nu ditémbongkeun dina daptar panjang awaskeuneun:",
+       "prefs-watchlist-edits": "Jumlah maksimum parobahan nu ditémbongkeun dina daptar awaskeuneun:",
        "prefs-watchlist-edits-max": "Panglobana: 1000",
        "prefs-watchlist-token": "Token awaskeuneun:",
        "prefs-misc": "Pangaturan rupa-rupa",
        "prefs-help-signature": "Pamanggih dina kaca sawala/obrolan kudu ditandaan \"<nowiki>~~~~</nowiki>\" nu bakal robah jadi tanda tangan anjeun jeung titimangsana.",
        "badsig": "Parafna teu valid; pariksa tag HTML-na geura.",
        "badsiglength": "Tawis leungeun panjang teuing. Kuduna kurang ti $1 {{PLURAL:$1|karaktér|karaktér}}.",
-       "yourgender": "Jenis kelamin:",
-       "gender-unknown": "Moal béja-béja",
+       "yourgender": "Anjeun leuwih resep didéskripsikeunna kumaha?",
+       "gender-unknown": "Nalika nyebut anjeun, pakakas lemes bakal ngagunakeun kekecapan nu nétral waktu diperlukeun",
        "gender-male": "Manéhna ngédit kaca wiki",
        "gender-female": "Manéhna ngédit kaca wiki",
        "email": "Surélék",
-       "prefs-help-realname": "* Ngaran asli (pilihan): mun anjeun milih ngeusian, bakal dipaké pikeun nandaan kontribusi anjeun.",
+       "prefs-help-realname": "Ngaran asli miwatek opsional.\nMun disayagakeun, ieu ngaran bisa dipaké pikeun méré atribusi kana karya anjeun.",
        "prefs-help-email": "Surélék sipatna pilihan, tapi diperlukeun pikeun nyetél ulang sandi lamun anjeun poho.",
        "prefs-help-email-others": "Anjeun ogé bisa milih sangkan bisa disuratan ngaliwatan kaca pamaké atawa obrolan tanpa kudu ngedalkeun saha anjeun sabenerna.",
        "prefs-help-email-required": "Alamat surélék dibutuhkeun.",
        "userrights-lookup-user": "Pilih saurang pamaké",
        "userrights-user-editname": "Asupkeun sandiasma:",
        "editusergroup": "Muat gorombolan pamaké",
-       "editinguser": "Ngarobah hak kontributor '''[[User:$1|$1]]''' $2",
-       "userrights-editusergroup": "Édit gorombolan pamaké",
+       "editinguser": "Ngarobah hak pamaké pikeun {{GENDER:$1|pamaké}} <strong>[[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "Édit gorombolan {{GENDER:$1|pamaké}}",
        "userrights-viewusergroup": "Témbong gorombolan {{GENDER:$1|pamaké}}",
        "saveusergroups": "Simpen gorombolan {{GENDER:$1|pamaké}}",
        "userrights-groupsmember": "Anggota ti:",
        "right-unblockself": "buka peungpeuk sorangan",
        "right-protect": "Ngarobah hambalan protéksi jeung édit kaca anu dikonci",
        "right-editprotected": "Edit kaca anu dikonci salaku \"{{int:protect-level-sysop}}\"",
+       "right-editcontentmodel": "Édit modél kontén kaca",
        "right-editinterface": "Édit antarbenget pamaké",
        "right-editusercss": "Édit berkas CSS pamaké séjén",
        "right-edituserjs": "Ngédit berkas JS pamaké séjén",
+       "right-editmyusercss": "Édit berkas CSS pamaké anjeun",
+       "right-editmyuserjs": "Édit berkas JavaScript pamaké anjeun",
        "right-viewmywatchlist": "Tempo awaskeuneun anjeun",
        "right-viewmyprivateinfo": "Tempo data pribadi anjeun (alamat surélék, ngaran asli)",
        "right-editmyprivateinfo": "Robah data pribadi anjeun (alamat surélék, ngaran asli)",
        "right-editmyoptions": "Robah préferénsi anjeun",
+       "right-markbotedits": "Nandaan pamalikan révisi minangka éditan bot",
+       "right-noratelimit": "Teu dipangaruhan ku wawatesan jumlah éditan",
        "right-import": "Ngimpor kaca ti wiki séjén",
        "right-importupload": "Ngimpor kaca tina hiji koropak nu dimuat",
        "right-patrol": "Nandaan éditan pamaké séjén minangka geus dipatroli",
        "right-autopatrol": "Ngédit kalayan status éditan sacara otomatis ditandaan geus dipatroli",
        "right-patrolmarks": "Tempo panandaan patroli nuanyar robah",
+       "right-unwatchedpages": "Tempo daptar kaca-kaca nu teu dipariksa",
        "right-mergehistory": "Ngagabungkeun jujutan kaca",
        "right-userrights": "Édit kabeh hak pamaké",
        "right-userrights-interwiki": "Ngédit hak kontributor di wiki lianna",
        "right-siteadmin": "Ngonci jeung muka konci databés",
+       "right-override-export-depth": "Ékspor kaca kaasup kaca-kaca patalian nepi ka kalaputan 5",
        "right-sendemail": "Kirim surélék ka pamaké séjén",
+       "right-managechangetags": "Jieun sarta pareuman [[Special:Tags|tag]]",
+       "right-applychangetags": "Tapelkeun [[Special:Tags|tag]] bareng jeun parobahan pamaké",
+       "right-changetags": "Tambah sarta pupus [[Special:Tags|tag]] arbitrari dina révisi masing-masing jeung éntri log",
+       "right-deletechangetags": "Pupus [[Special:Tags|tag]] tina basisdata",
        "grant-generic": "\"$1\" buntel hak aksés",
        "grant-group-page-interaction": "Interaksi jeung kaca",
        "grant-group-file-interaction": "Interaksi jeung média",
        "grant-group-watchlist-interaction": "Interaksi jeung aawaseun anjeun",
        "grant-group-email": "Kirim surélék",
+       "grant-group-high-volume": "Ngalampahan aktivitas nu loba pisan",
+       "grant-group-customization": "Kustomisasi jeung préférénsi",
+       "grant-group-administration": "Ngalakokan tarékah administratif",
        "grant-group-private-information": "Aksés data pribadi ngeunaan anjeun",
        "grant-group-other": "Lalampahan macem-macem",
        "grant-blockusers": "Peungpeuk sarta teu meungpeuk pamaké",
        "grant-createaccount": "Jieun akun",
        "grant-createeditmovepage": "Jieun, édit, jeung alihkeun kaca",
        "grant-delete": "Pupus kaca, révisi, jeung éntri log",
+       "grant-editinterface": "Ngédit ngaranspasi MediaWiki sarta CSS/JavaScript pamaké",
        "grant-editmycssjs": "Édit CSS/JavaScript pamaké anjeun",
        "grant-editmyoptions": "Édit préférénsi pamaké anjeun",
        "grant-editmywatchlist": "Édit awaskeuneun anjeun",
        "grant-basic": "Aksés dasar",
        "grant-viewdeleted": "Tempo berkas jeung kaca nu dihapus",
        "grant-viewmywatchlist": "Tempo aawaseun anjeun",
+       "grant-viewrestrictedlogs": "Tempo éntri log kawates",
        "newuserlogpage": "Log akun anyar",
        "newuserlogpagetext": "Di handap ieu béréndélan log pamaké anyar.",
        "rightslog": "Log hak pamaké",
        "action-deletedtext": "témbong téks révisi anu dihapus",
        "action-browsearchive": "milari kaca nu geus dihapus",
        "action-undelete": "bolaykeun pamupusan kaca",
-       "action-suppressrevision": "mariksa jeung nyimpen deui ieu révisi nyumput",
+       "action-suppressrevision": "riksa sarta balikkeun révisi nu disamunikeun",
        "action-suppressionlog": "nempo ieu log pribadi",
        "action-block": "meungpeuk ieu pamaké tina ngédit",
        "action-protect": "ngarobah hambalan konci ieu kaca",
+       "action-rollback": "geuwat malikkeun éditan-éditan pamaké panungtung nu ngédit kaca tinangtu",
        "action-import": "impor kaca ti wiki séjén",
+       "action-importupload": "ngimpor ieu kaca tina pamuatan berkas",
        "action-patrol": "Nandaan éditan séjén minangka geus diroris",
        "action-autopatrol": "tandaan éditan anjeun salaku geus diroris",
        "action-unwatchedpages": "témbongkeun béréndélan kaca nu teu diawaskeun",
        "action-viewmyprivateinfo": "tempo émbaran pribadi anjeun",
        "action-editmyprivateinfo": "robah émbaran pribadi anjeun",
        "action-editcontentmodel": "ngédit modél kontén hiji kaca",
+       "action-managechangetags": "jieun sarta pareuman tag",
+       "action-applychangetags": "larapkeun tag bareng jeung parobahan anjeun",
+       "action-deletechangetags": "pupus tag tina basis data",
        "action-purge": "hapus sindangan ieu kaca",
        "nchanges": "$1 {{PLURAL:$1|parobahan|parobahan}}",
+       "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|saprak sorang panungtung}}",
        "enhancedrc-history": "jujutan",
        "recentchanges": "Anyar robah",
        "recentchanges-legend": "Pilihan parobahan anyar",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (tempo ogé [[Special:NewPages|béréndélan kaca anyar]])",
        "recentchanges-submit": "Témbongkeun",
        "rcfilters-tag-remove": "Pupus '$1'",
+       "rcfilters-legend-heading": "<strong>Daptar singgetan:</strong>",
        "rcfilters-other-review-tools": "<strong>Pakakas paninjauan lianna</strong>",
        "rcfilters-group-results-by-page": "Gorombolkeun hasil dumasar kaca",
        "rcfilters-grouping-title": "Gorombolkeun",
        "rcfilters-limit-shownum": "Pidangkeun {{PLURAL:$1|parobahan|$1 parobahan}} panungtung",
        "rcfilters-days-title": "Poé-poé panungtung",
        "rcfilters-hours-title": "Jam-jam panungtung",
+       "rcfilters-days-show-days": "$1 {{PLURAL:$1|sapoé|poé}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|sajam|jam}}",
+       "rcfilters-highlighted-filters-list": "Disorot: $1",
        "rcfilters-quickfilters": "Saringan kasimpen",
+       "rcfilters-quickfilters-placeholder-title": "Can aya tutumbu kasimpen",
        "rcfilters-savedqueries-defaultlabel": "Saringan kasimpen",
        "rcfilters-savedqueries-rename": "Robah ngaran",
+       "rcfilters-savedqueries-setdefault": "Atur minangka baku",
+       "rcfilters-savedqueries-unsetdefault": "Pupus minangka baku",
        "rcfilters-savedqueries-remove": "Hapus",
        "rcfilters-savedqueries-new-name-label": "Ngaran",
+       "rcfilters-savedqueries-new-name-placeholder": "Émbarkeun tujuan ieu panyaringan",
+       "rcfilters-savedqueries-apply-label": "Nyieun panyaringan",
+       "rcfilters-savedqueries-apply-and-setdefault-label": "Jieun panyaringan baku",
        "rcfilters-savedqueries-cancel-label": "Bolay",
        "rcfilters-savedqueries-add-new-title": "Simpen pangaturan ieu saringan",
        "rcfilters-restore-default-filters": "Balikeun panyaringan bawaan",
        "rcfilters-clear-all-filters": "Pupus sakumna panyaringan",
+       "rcfilters-show-new-changes": "Témbongkeun parobahan panganyarna",
        "rcfilters-search-placeholder": "Saring parobahan panganyarna (langlang atawa mimitian ngetik)",
        "rcfilters-invalid-filter": "Panyaringan teu sah",
+       "rcfilters-empty-filter": "Euweuh filter aktif. Sakumna kontribusi ditémbongkeun.",
        "rcfilters-filterlist-title": "Saringan",
        "rcfilters-filterlist-whatsthis": "Kumaha bet bisa kieu?",
        "rcfilters-filterlist-feedbacklink": "Béré eupan balik pikeun saringan uji coba anyar",
        "rcfilters-filter-user-experience-level-unregistered-label": "Teu kadaptar",
        "rcfilters-filter-user-experience-level-unregistered-description": "Éditor nu teu asup log.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Padatang anyar",
+       "rcfilters-filter-user-experience-level-newcomer-description": "Éditor kadaptar nu miboga éditan kurang ti 10 éditan sarta aktivitas salila 4 poé.",
        "rcfilters-filter-user-experience-level-learner-label": "Palajar",
+       "rcfilters-filter-user-experience-level-learner-description": "Éditor kadaptar nu pangalamanna aya di antara \"padatang anyar\" jeung \"pamaké pangpangalaman\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Pamaké pangpangalaman",
        "rcfilters-filter-user-experience-level-experienced-description": "Éditor kadaptar nu leuwih ti 500 éditan sarta aktivitas salila 30 poé.",
        "rcfilters-filtergroup-automated": "Kontribusi otomatis",
        "rcfilters-filter-unpatrolled-description": "Éditan teu ditandaan geus diaawas.",
        "rcfilters-filtergroup-significance": "Kapentingan",
        "rcfilters-filter-minor-label": "Éditan minor",
+       "rcfilters-filter-minor-description": "Éditan nu ditandaan éditor minangka éditan minor.",
        "rcfilters-filter-major-label": "Lain éditan minor",
        "rcfilters-filter-major-description": "Éditan teu ditandaan minangka minor.",
        "rcfilters-filtergroup-watchlist": "Kaca kaawas",
        "rcfilters-filter-logactions-label": "Tarékah kacatet",
        "rcfilters-filtergroup-lastRevision": "Révisi panganyarna",
        "rcfilters-filter-lastrevision-label": "Révisi panganyarna",
+       "rcfilters-filter-lastrevision-description": "Ngan parobahan panganyarna di ieu kaca.",
        "rcfilters-filter-previousrevision-label": "Lain révisi panganyarna",
        "rcfilters-filter-previousrevision-description": "Sakabéh parobahan nu lain mangrupa \"révisi panganyarna\".",
        "rcfilters-filter-excluded": "Diiwalkeun",
        "rcfilters-liveupdates-button-title-on": "Pareuman parobahan langsung",
        "rcfilters-liveupdates-button-title-off": "Témbongkeun parobahan anyar nalika éta parobahan prung",
        "rcfilters-watchlist-markSeen-button": "Tandaan sakumna parobahan minangka katémbong",
+       "rcfilters-watchlist-editWatchlist-button": "Édit daptar kaca awaskeuneun anjeun",
        "rcnotefrom": "Di handap ieu {{PLURAL:$5|parobahan}} ti <strong>$3, $4</strong> (nepi ka <strong>$1</strong> ditémbongkeun).",
        "rclistfromreset": "Rését pilihan kaping",
        "rclistfrom": "Témbongkeun nu anyar robah nepi ka $3 $2",
        "uploadtext": "Gunakeun formulir di handap pikeun ngunjal berkas.\nPikeun midangkeun atawa maluruh berkas anu saméméhna diunjal, sorang [[Special:FileList|daptar berkas]]. Unjalan (ulang) ogé kacatet kana [[Special:Log/upload|log unjalan]], sedengkeun pamupusan mah kacatetna dina [[Special:Log/delete|log pamupusan]].\n\nPikeun midangkeun atawa nerapkeun berkas kana kaca mah migunakeun tutumbu salah sahiji format di handap:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Berkas.jpg]]</nowiki></code>''' pikeun midangkeun berkas dina ukuran aslina\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Berkas.png|200px|thumb|left|téks alternatif]]</nowiki></code>''' pikeun midangkeun berkas nu rubakna 200px na jero kotak, nitih di kéncaeun kaca bari aya tulisan 'téks alternatif' minangka kamandang gambar\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Berkas.ogg]]</nowiki></code>''' minangka tutumbu langsung kana berkas anu dimaksud tanpa kudu midangkeun éta berkas liwat wiki",
        "upload-permitted": "{{PLURAL:$2|Jinis}} berkas nu diheugbaékeun: $1.",
        "upload-preferred": "Tipeu koropak nu dianjurkeun: $1.",
-       "upload-prohibited": "Tipeu koropak nu dicaram: $1.",
+       "upload-prohibited": "{{PLURAL:$2|Jinis}} berkas nu dilarang: $1.",
        "uploadlogpage": "Log unggahan",
        "uploadlogpagetext": "Di handap mangrupa daptar muatan koropak nu panganyarna. Titimangsa nu katémbong dumasar titimangsa server.",
        "filename": "Ngaran koropak",
        "empty-file": "Berkas nu dikirim kosong.",
        "file-too-large": "Berkas nu dikirim gedé teuing.",
        "filename-tooshort": "Ngaran berkas pondok teuing.",
-       "filetype-banned": "Jenis berkas ieu dipahing.",
+       "filetype-banned": "Jinis berkas ieu dipahing.",
        "verification-error": "Ieu berkas teu lulus vérifikasi.",
        "hookaborted": "Modifikasi nu coba dipilampah ku anjeun dibolaykeun ku hiji kaitan éksténsi.",
        "illegal-filename": "Ngaran berkas dipahing.",
        "upload-proto-error": "Salah protokol",
        "upload-file-error": "Kasalahan internal",
        "upload-misc-error": "Kasalahan muat anu teu kanyahoan",
+       "upload-too-many-redirects": "URL ngandung loba teuing alihan",
        "upload-http-error": "Aya galat HTTP: $1",
+       "upload-copy-upload-invalid-domain": "Unjalan téplakan teu sayaga ti ieu domain.",
+       "upload-foreign-cant-upload": "Ieu wiki teu diatur pikeun ngunjal berkas ka gudang panyindangan asing.",
        "upload-dialog-title": "Unjal berkas",
        "upload-dialog-button-cancel": "Bolay",
        "upload-dialog-button-back": "Balik deui",
        "backend-fail-stream": "Teu bisa malidkeun berkas \"$1\"",
        "backend-fail-backup": "Teu bisa nyadangkeun berkas \"$1\".",
        "backend-fail-notexists": "Berkas $1 euweuh.",
+       "backend-fail-hashes": "Teu bisa meunang hash berkas minangka babandingan.",
        "backend-fail-notsame": "Berkas anu teu-identik geus aya di $1.",
        "backend-fail-invalidpath": "$1 perenahna henteu absah.",
        "backend-fail-delete": "Teu bisa ngahapus berkas $1.",
        "backend-fail-read": "Teu bisa maca berkas $1.",
        "backend-fail-create": "Teu bisa nulis berkas \"$1\".",
        "backend-fail-maxsize": "Teu bisa nulis berkas \"$1\" kusabab leuwih ti {{PLURAL:$2|hiji bit|$2 bit}}.",
+       "backend-fail-connect": "Teu bisa nyambungkeun kana panyimpenan backend \"$1\".",
+       "backend-fail-internal": "Aya kasalahan nu teu dipikawanoh di backend panyimpenan \"$1\".",
        "backend-fail-contenttype": "Teu bisa nangtukeun tipeu eusi berkas anu badé disimpen di \"$1\".",
        "lockmanager-notlocked": "Teu bisa muka konci \"$1\" kusabab teu dikonci.",
        "lockmanager-fail-closelock": "Teu bisa nutup berkas konci pikeun \"$1\".",
        "lockmanager-fail-deletelock": "Teu bisa ngahapus berkas konci pikeun \"$1\".",
+       "lockmanager-fail-acquirelock": "Teu bisa meunang pangoncian pikeun \"$1\"",
        "lockmanager-fail-openlock": "Teu bisa muka berkas konci pikeun \"$1\".",
        "lockmanager-fail-releaselock": "Teu bisa ngaleupaskeun konci pikeun \"$1\".",
        "lockmanager-fail-db-release": "Teu bisa ngaleupaskeun konci dina databés $1.",
+       "lockmanager-fail-svr-acquire": "Gagal nyokot konci di servér $1.",
        "lockmanager-fail-svr-release": "Teu bisa ngaleupaskeun konci dina server $1.",
        "zip-wrong-format": "Berkas anu dipilih lain berkas ZIP.",
        "uploadstash": "Unjal engkeunan",
        "img-auth-streaming": "Palidan \"$1\".",
        "img-auth-noread": "Pamaké teu boga kawenangan maca \"$1\".",
        "http-invalid-url": "URL teu bener: $1",
-       "http-invalid-scheme": "URL anu skémana \"$1\" teu karojong",
+       "http-invalid-scheme": "URL kalawan skéma \"$1\" teu dirojong.",
        "http-read-error": "Kasalahan maca HTTP.",
        "http-timed-out": "Pamundutan HTTP béakeun waktu.",
        "http-curl-error": "Kasalahan keur nyokot URL: $1",
        "nolicense": "Taya nu dipilih",
        "licenses-edit": "Robah pilihan lisénsi",
        "license-nopreview": "(euweuh pramidang)",
-       "upload_source_url": "(URL nu sohéh sarta bisa dibuka ku umum)",
-       "upload_source_file": " (koropak dina komputer salira)",
+       "upload_source_url": "(Anjeun milih berkas ti URL valid nu bisa diaksés publik)",
+       "upload_source_file": "(berkas pinilih anjeun tina komputer anjeun)",
        "listfiles-delete": "hapus",
        "listfiles-summary": "Ieu kaca husus némbongkeun sakabéh berkas anu geus diunggah.",
        "listfiles_search_for": "Paluruh ngaran média:",
        "listfiles_size": "Badagna",
        "listfiles_description": "Pedaran",
        "listfiles_count": "Vérsi",
+       "listfiles-show-all": "Kaasup vérsi heubeul gambar",
        "listfiles-latestversion": "Vérsi ayeuna",
        "listfiles-latestversion-yes": "Enya",
        "listfiles-latestversion-no": "Henteu",
        "imagelinks": "Pamakéan berkas",
        "linkstoimage": "Kaca ieu  {{PLURAL:$1|numbu|$1 numbu}} ka gambar ieu :",
        "nolinkstoimage": "Teu aya kaca anu nutumbu ka ieu berkas.",
+       "linkstoimage-redirect": "$1 (pangalihan berkas) $2",
        "sharedupload": "Ieu koropak téh ti $1 nu bisa jadi dipaké ku proyék-proyék lianna.",
        "sharedupload-desc-here": "Ieu berkas asalna ti $1 anu bisa jadi dipaké ku proyék séjén. \nPedaran ti [$2 kaca pedaranana] dipidangkeun di handap.",
        "filepage-nofile": "Taya berkas nu ngaranna kieu.",
        "uploadnewversion-linktext": "ngamuatkeun vérsi anyar koropak ieu",
        "shared-repo-from": "ti $1",
+       "shared-repo": "hiji répositori réréongan",
        "upload-disallowed-here": "Anjeun teu bisa nimpah ieu berkas.",
        "filerevert": "balikkeun $1",
        "filerevert-legend": "Balikkeun gambar",
        "unwatchedpages": "Kaca nu teu diawaskeun",
        "listredirects": "Daptar alihan",
        "listduplicatedfiles": "Daptar berkas duplikat.",
+       "listduplicatedfiles-summary": "Ieu téh daptar berkas nu mana vérsi panganyarna tina éta berkas mangrupa duplikat tina vérsi panganyarna ti sawatara berkas lian. Ngan berkas lokal nu dianggap mah.",
        "unusedtemplates": "Citakan nu teu kapaké",
        "unusedtemplatestext": "Ieu kaca ngabéréndélkeun sakabéh kaca dina rohang ngaran {{ns:template}} anu teu diwengku ku kaca séjén.\nSaméméh ngahapus, pariksa heula bisi aya tumbu ka ieu citakan.",
        "unusedtemplateswlh": "tutumbu lianna",
        "randompage": "Kaca acak",
-       "randompage-nopages": "Euweuh kaca dina ieu spasi ngaran \"$1\".",
+       "randompage-nopages": "Euweuh kaca dina {{PLURAL:$2|ngaranspasi}} di handap: $1.",
+       "randomincategory": "Kaca acak dina kategori",
+       "randomincategory-invalidcategory": "\"$1\" lain ngaran kategori nu lumaku.",
+       "randomincategory-nopages": "Euweuh kaca dina [[:Category:$1]].",
        "randomincategory-category": "Kategori:",
        "randomincategory-legend": "Kaca acak dina kategori",
        "randomincategory-submit": "Jung",
        "statistics-users-active": "Pamaké getol",
        "statistics-users-active-desc": "Kontributor nu ngoprék salila {{PLURAL:$1|poé|$1 poé}} panungtung",
        "pageswithprop": "Kaca ku kaca properti",
+       "pageswithprop-legend": "Kaca kalawan kaca properti",
+       "pageswithprop-text": "Ieu kaca eusina daptar kaca nu ngagunakeun properti kaca tinangtu.",
        "pageswithprop-prop": "Ngarab properti:",
+       "pageswithprop-reverse": "Susunkeun dina susunan tibalik",
+       "pageswithprop-sortbyvalue": "Susunkeun sumasar eusi properti",
        "pageswithprop-submit": "Jung",
+       "pageswithprop-prophidden-long": "eusi properti téks panjang nyamuni ($1)",
+       "pageswithprop-prophidden-binary": "eusi properti binér nyamuni ($1)",
        "doubleredirects": "Alihan ganda",
        "doubleredirectstext": "Ieu kaca ngabéréndélkeun kaca-kaca alihan ka kaca alihan lianna. Unggal baris ngandung tutumbu ka alihan kahiji jeung kadua, ogé tujul alihan kadua anu biasana tujul kaca anu \"bener\", anu sakuduna dituju ku alihan kahiji. Ëntri nu <del>dicorét</del> geus diropéa.",
-       "double-redirect-fixed-move": "[[$1]] geus pindah, dialihkeun ka [[$2]].",
-       "double-redirect-fixed-maintenance": "Ngoméan alihan ganda ti [[$1]] ka [[$2]].",
+       "double-redirect-fixed-move": "[[$1]] geus dipindahkeun.\nKami geus nganyarkeunna sacara otomatis jeung deuih kiwari mah jadi kaca alihan ka [[$2]].",
+       "double-redirect-fixed-maintenance": "Ngomékeun alihan ganda ti [[$1]] ka [[$2]] dina hiji pamulasaraan.",
        "double-redirect-fixer": "Pangomé alihan",
        "brokenredirects": "Alihan buntu",
        "brokenredirectstext": "Alihan di handap numbu ka kaca nu teu aya:",
        "wantedpages": "Kaca nu dipikabutuh",
        "wantedpages-badtitle": "Judul teu sah dina kumpulan hasil: $1",
        "wantedfiles": "Berkas nu dipikabutuh",
+       "wantedfiletext-nocat-noforeign": "Berkas-berkas di handap téh dipaké, ngan euweuh.",
        "wantedtemplates": "Citakan nu dipikabutuh",
        "mostlinked": "Nu panglobana numbu ka kaca séjén",
        "mostlinkedcategories": "Paling loba ditumbukeun ka kategori",
        "mostlinkedtemplates": "Citakan nu panglobana dipaké (ditransklusikeun)",
        "mostcategories": "Artikel nu paling loba ngandung kategori",
        "mostimages": "Berkas anu panglobana ditumbukeun",
+       "mostinterwikis": "Kaca kalawan interwiki panglobana",
        "mostrevisions": "Artikel nu pangmindengna dirévisi",
        "prefixindex": "Kabeh kaca maké awalan",
+       "prefixindex-namespace": "Sakum kaca kalawan awalan (ngaranspasi $1)",
        "prefixindex-submit": "Témbongkeun",
+       "prefixindex-strip": "Strip awalan dina daptar",
        "shortpages": "Kaca-kaca parondok",
        "longpages": "Kaca-kaca paranjang",
        "deadendpages": "Kaca buntu",
        "deadendpagestext": "Kaca-kaca di handap ieu teu numbu ka kaca séjén di {{SITENAME}}:",
        "protectedpages": "Kaca-kaca nu dikonci",
        "protectedpages-indef": "Ngan pikeun panangtayungan kalawan waktu nuteu kawates",
+       "protectedpages-cascade": "Ngan raksaan runtuy",
        "protectedpages-noredirect": "Sumputkeun pangalihan",
        "protectedpagesempty": "Dina danget ieu, teu aya kaca nu dikonci dumasar kana ieu paraméter.",
        "protectedpages-timestamp": "Cap titimangsa",
        "apihelp": "Pitulung API",
        "apihelp-no-such-module": "Modul \"$1\" teu kapanggih.",
        "apisandbox": "Kotrétan API",
+       "apisandbox-jsonly": "JavaScript diperlukeun pikeun maké kotrétan API.",
        "apisandbox-api-disabled": "API dipareuman dina ieu situs.",
        "apisandbox-fullscreen": "Mekarkeun panél",
+       "apisandbox-fullscreen-tooltip": "Kembangkeun panel kotrétan pikeun ngeusian jandéla panyungsi.",
        "apisandbox-unfullscreen": "Témbongkeun kaca",
        "apisandbox-submit": "Jieun pundutan",
        "apisandbox-reset": "Bersihan",
        "apisandbox-sending-request": "Ngirim pundutan API...",
        "apisandbox-loading-results": "Nampa hasil API...",
        "apisandbox-request-selectformat-label": "Témbongkeun pundutan data minangka:",
+       "apisandbox-request-format-url-label": "String kuéri URL",
        "apisandbox-request-url-label": "URL pundutan:",
        "apisandbox-request-json-label": "Pundut JSON:",
+       "apisandbox-request-time": "Wayah pamundutan: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-results-fixtoken": "Oméan token sarta kirim deui",
+       "apisandbox-results-fixtoken-fail": "Gagal meunangkeun token \"$1\".",
+       "apisandbox-alert-page": "Kolom jeung ieu kaca téh teu valid.",
+       "apisandbox-alert-field": "Niléy dina ieu kolom téh teu valid.",
        "apisandbox-continue": "Tuluykeun",
        "apisandbox-continue-clear": "Bersihan",
+       "apisandbox-param-limit": "Asupkeun <kbd>max</kbd> pikeun ngagunakeun wates maksimum.",
+       "apisandbox-multivalue-all-namespaces": "$1 (Sakum ngaranspasi)",
        "apisandbox-multivalue-all-values": "$1 (Kabéh ajén)",
        "booksources": "Sumber pustaka",
        "booksources-search-legend": "Paluruh sumber buku",
        "booksources-search": "Paluruh",
        "booksources-text": "Di handap ieu ngabéréndélkeun tutumbu ka loka-loka nu ngical buku, boh nu anyar atawa loakan, nu sugan uninga kana buku anu nuju dipilari:",
        "booksources-invalid-isbn": "Sigana ISBN-na teu bener; pariksa deui bisi aya salah téplak ti sumber aslina.",
+       "magiclink-tracking-rfc": "Kaca ngagunakeun tutumbu magis RFC",
+       "magiclink-tracking-pmid": "Kaca ngagunakeun tutumbu magis PMID",
+       "magiclink-tracking-isbn": "Kaca ngagunakeun tutumbu magis ISBN",
        "specialloguserlabel": "Palaku:",
        "speciallogtitlelabel": "Sasaran (judul atawa {{ns:user}}:sandiasma pikeun pamaké):",
        "log": "Log",
        "alllogstext": "Béréndélan sakabéh log nu aya di {{SITENAME}}.\nBisa dipondokkeun ku cara milih tipe log, ngaran pamaké, atawa kaca nu dimaksud.",
        "logempty": "Taya item nu cocog dina log.",
        "log-title-wildcard": "Téangan judul nu dimimitian ku tulisan ieu",
+       "showhideselectedlogentries": "Robah katempo henteuna éntri log pinilih",
+       "log-edit-tags": "Édit tag tina éntri log pinilih",
        "checkbox-select": "Pilih: $1",
        "checkbox-all": "Sakumna",
        "checkbox-none": "Kosong",
        "allpagesbadtitle": "Judul kaca nu dibikeun teu bener atawa mibanda awalan antarbasa atawa antarwiki, nu ngandung karakter nu teu bisa dipaké dina judul.",
        "allpages-bad-ns": "{{SITENAME}} teu boga spasi ngaran \"$1\".",
        "allpages-hide-redirects": "Sumputkeun pangalihan",
+       "cachedspecial-viewing-cached-ttl": "Anjeun nempo vérsi kés ieu kaca, nya meureun geus lilana $1.",
+       "cachedspecial-viewing-cached-ts": "Anjeun nempo vérsi kés ieu kaca, nya meureun moal bener-bener aktual.",
        "cachedspecial-refresh-now": "Tempo nu panganyarna.",
        "categories": "Kategori",
        "categories-submit": "Témbongkeun",
        "activeusers-intro": "Ieu béréndélan kontributor anu geus ngoprék $1 {{PLURAL:$1|poé|poé}} panungtung.",
        "activeusers-count": "$1 {{PLURAL:$1|aktivitas}} dina {{PLURAL:$3|1 hari|$3 hari}} panungtung",
        "activeusers-from": "Témbongkeun kontributor dimimitian ku:",
+       "activeusers-groups": "Témbongkeun pamaké nu kaasup gorombolan:",
+       "activeusers-excludegroups": "Samunikeun pamaké nu kaasup gorombolan:",
        "activeusers-noresult": "Teu kapendak.",
+       "activeusers-submit": "Témbongkun pamaké aktif",
        "listgrouprights": "Hak-hak grup pamaké",
        "listgrouprights-summary": "Ieu mangrupa daptar jumplukan pamaké anu aya di wiki ieu, kalawan daptar hak aksés maranéhanana.\nÉmbaran leuwih luyu ngeunaan hak pamaké bisa ditingali di [[{{MediaWiki:Listgrouprights-helppage}}|dieu]].",
-       "listgrouprights-key": "* <span class=\"listgrouprights-granted\">Hak anu dipimilik</span>\n* <span class=\"listgrouprights-revoked\">Hak anu dicabut</span>",
+       "listgrouprights-key": "Jujutan:\n* <span class=\"listgrouprights-granted\">Hak anu dipimilik</span>\n* <span class=\"listgrouprights-revoked\">Hak anu dicabut</span>",
        "listgrouprights-group": "Jumplukan",
        "listgrouprights-rights": "Hak",
        "listgrouprights-helppage": "Help:Hak Jumplukan",
        "listgrouprights-removegroup-self": "Piceun {{PLURAL:$2|grup|grups}} ti akun sorangan: $1",
        "listgrouprights-addgroup-self-all": "Tambahkeun sakabéh grup ka akun sorangan",
        "listgrouprights-removegroup-self-all": "Piceun sakabéh grup ti akun sorangan",
+       "listgrouprights-namespaceprotection-header": "Watesan ngaranspasi",
+       "listgrouprights-namespaceprotection-namespace": "Ngaranspasi",
+       "listgrouprights-namespaceprotection-restrictedto": "Hak nu ngawidian pamaké pikeun ngédit",
        "listgrants": "Idin",
        "listgrants-grant": "Idin",
        "listgrants-rights": "Hak",
        "trackingcategories-msg": "Kategori palacak",
        "trackingcategories-name": "Ngaran talatah",
        "trackingcategories-desc": "Kritéria inklusi kategori",
+       "restricted-displaytitle-ignored": "Kaca-kaca kalawan pangantepan pidangan judul",
        "trackingcategories-nodesc": "Taya pedaran nu sayaga",
        "trackingcategories-disabled": "Kategori dipareuman",
        "mailnologin": "Euweuh alamat ngirim",
        "emailsent": "Surélék geus dikirim",
        "emailsenttext": "Surélék anjeun geus dikirim.",
        "emailuserfooter": "Ieu surélék dikirim ku $1 ka $2 migunakeun fungsi \"Surélékan pamaké ieu\" di {{SITENAME}}.",
-       "usermessage-summary": "Ninggalkeun talatah sistem.",
+       "usermessage-summary": "Ninggalkeun talatah sistim.",
        "usermessage-editor": "Talatah sistim",
        "watchlist": "Awaskeuneun",
        "mywatchlist": "Awaskeuneun",
        "watchnologin": "Can asup log",
        "addwatch": "Tambahkeun ka aawaseun",
        "addedwatchtext": "Kaca \"[[:$1]]\" geus ditambahkeun ka [[Special:Watchlist|awaskeuneun]] anjeun.\nJaga, parobahan na kaca ieu katut kaca obrolanana bakal dibéréndélkeun di dinya, sarta kacana bakal katémbong '''dikandelan''' dina kaca [[Special:RecentChanges|Nu anyar robah]] sangkan leuwih gampang ngawaskeunana.\n\n<p>Mun jaga anjeun moal deui ngawaskeun parobahan na kaca éta, klik tumbu \"Eureun ngawaskeun\" na lajursisi.",
+       "addedwatchtext-short": "Kaca \"$1\" geus ditambahkeun kana daptar aawaseun anjeun.",
        "removewatch": "Piceun tina béréndélan awaskeuneun",
        "removedwatchtext": "Kaca \"[[:$1]]\" geus dikaluarkeun tina [[Special:Watchlist|daptar awaskeuneun]] anjeun.",
        "removedwatchtext-short": "Kaca \"$1\" geus dipiceun tina béréndélan awaskeuneun.",
        "notanarticle": "Sanés kaca eusi",
        "notvisiblerev": "Révisi geus dihapus",
        "watchlist-details": "Aya {{PLURAL:$1|$1 kaca|$1 kaca}} dina béréndélan awaskeuneun, teu kaasup kaca obrolan/sawala.",
-       "wlheader-enotif": "Pangémbar surélék difungsikeun.",
+       "wlheader-enotif": "Iber surélék diaktifkeun.",
        "wlheader-showupdated": "Kaca nu robah ti panungtungan anjeun sindang ditémbongkeun kalawan '''kandel'''",
        "wlnote": "Di handap ieu mangrupa $1 {{PLURAL:$1|robahan|robahan}} ahir salila '''$2''' jam.",
        "wlshowlast": "Témbongkeun $1 jam $2 poé panungtung",
        "enotif_lastdiff": "Pikeun nempo parobahan, ilikan $1",
        "enotif_anon_editor": "pamaké anonim $1",
        "enotif_body": "Sadérék $WATCHINGUSERNAME,\n\nKaca $PAGETITLE na {{SITENAME}} geus $CHANGEDORCREATED tanggal $PAGEEDITDATE ku $PAGEEDITOR. Mangga tingal {{SERVER}}{{localurl:$PAGETITLE}} pikeun vérsi kiwari.\n\n$NEWPAGE\n\nRingkesan éditor: $PAGESUMMARY $PAGEMINOREDIT\n\nKontak éditor:\nsurat {{SERVER}}{{localurl:Husus:Emailuser|target=$PAGEEDITOR}}\nwiki {{SERVER}}{{localurl:Pamaké:$PAGEEDITOR}}\n\nMun anjeun teu sindang deui ka ieu kaca, parobahan salajengna moal diémbarkeun. Anjeun bisa ogé nyetél deui umbul-umbul pikeun sadaya kaca nu aya na daptar awaseun anjeun.\n\n             Sistim émbaran {{SITENAME}} pikeun anjeun\n\n--\nPikeun ngarobah setélan dabtar awaseun anjeun, sindang ka {{SERVER}}{{localurl:Husus:Watchlist|edit=yes}}\n\nAsupan jeung bantuan salajengna:\n$HELPPAGE",
+       "enotif_minoredit": "Ieu mah éditan minor",
        "created": "geus dijieun",
        "changed": "geus robah",
        "deletepage": "Hapus kaca",
        "confirm": "Konfirmasi",
        "excontent": "eusina nu heubeul: '$1'",
-       "excontentauthor": "eusina: \"$1\" (nu ditulis ku \"[[Special:Contributions/$2|$2]]\" wungkul)",
+       "excontentauthor": "eusina ngan mangrupa: \"$1\", sarta hiji-hijina kontributor téh nyaéta \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|obrolan]])",
        "exbeforeblank": "eusi méméh dikosongkeun nyéta: '$1'",
        "delete-confirm": "Hapus \"$1\"",
        "delete-legend": "Hapus",
        "delete-edit-reasonlist": "Alesan ngahapus éditan",
        "delete-toobig": "Jujutan édit ieu kaca panjang pisan, leuwih ti {{PLURAL:$1|révisi|révisi}}.\nHal ieu teu diwenangkeun pikeun nyegah karuksakan {{SITENAME}} nu teu dihaja.",
        "delete-warning-toobig": "Jujutan ieu kaca panjang pisan, leuwih ti{{PLURAL:$1|révisi|révisi}}. Dihapusna ieu kaca bisa ngaruksak jalanna pangkalan data {{SITENAME}}; sing ati-ati.",
+       "deleteprotected": "Anjeun teu bisa mupus ieu kaca lantaran geus ditangtayungan.",
        "rollback": "Balikkeun éditan",
        "rollbacklink": "balikkeun",
        "rollbacklinkcount": "balikkeun $1 {{PLURAL:$1|éditan}}",
+       "rollbacklinkcount-morethan": "balikkeun leuwih ti $1 {{PLURAL:$1|saéditan|éditan}}",
        "rollbackfailed": "Gagal malikkeun",
+       "rollback-missingparam": "Paraméter diperlukeun nalika dupundut teu sayaga.",
        "rollback-missingrevision": "Teu bisa muatkeun révisi data.",
        "cantrollback": "Éditan teu bisa dibalikkeun; kontribusi panungtung ngarupakeun hiji-hijina panulis kaca ieu.",
        "alreadyrolled": "Teu bisa mulangkeun édit ahir [[$1]] ku [[User:$2|$2]] ([[User talk:$2|Obrolan]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); geus aya nu ngédit atawa mulangkeun kacana.\n\nÉdit ahir ku [[User:$3|$3]] ([[User talk:$3|Obrolan]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "rollback-success": "Mulangkeun éditan $1; balik deui ka vérsi panungtung ku $2.",
        "sessionfailure-title": "Kagagalan sési",
        "sessionfailure": "Sigana aya masalah jeung termin log anjeun; peta ieu geus dibolaykeun salaku pépéling pikeun ngalawan ayana pangbajak. Mangga pencét \"back\" jeung muat ulang ti kaca asal anjeun, lajeng cobaan deui.",
+       "changecontentmodel": "Robah modél kontén hiji kaca",
        "changecontentmodel-legend": "Robah modél kontén",
        "changecontentmodel-title-label": "Judul kaca",
        "changecontentmodel-model-label": "Modél kontén anyar",
        "changecontentmodel-reason-label": "Alesan:",
        "changecontentmodel-submit": "Parobahan",
        "changecontentmodel-success-title": "Modél ieu kontén geus dirobah",
+       "changecontentmodel-success-text": "Jinis kontén [[:$1]] geus dirobah",
+       "changecontentmodel-nodirectediting": "Modél kontén $1 teu ngarojong éditan langsung",
        "changecontentmodel-emptymodels-title": "Taya modél kontén nu sayaga",
+       "changecontentmodel-emptymodels-text": "Kontén dina [[:$1]] teu bisa dirobah kana jinis lian.",
        "log-name-contentmodel": "Log parobahan modél kontén",
        "logentry-contentmodel-change-revertlink": "balikkeun",
        "logentry-contentmodel-change-revert": "balikkeun",
        "unprotectedarticle": "muka konci tina \"[[$1]]\"",
        "protectedarticle-comment": "{{GENDER:$2|Nangtayungan}} \"[[$1]]\"",
        "protect-title": "Ngonci \"$1\"",
+       "protect-title-notallowed": "Tempo undak panangtayungan ti \"$1\"",
        "prot_1movedto2": "mindahkeun [[$1]] ka [[$2]]",
+       "protect-badnamespace-title": "Ngaranspasi nu teu bisa ditangtayungan",
        "protect-norestrictiontypes-title": "Kaca nu teu karaksa",
        "protect-legend": "Konfirmasi ngonci",
        "protectcomment": "Alesan:",
        "protectexpiry": "Kadaluwarsa",
        "protect_expiry_invalid": "Waktu kadaluwarsa teu sah.",
        "protect_expiry_old": "Waktu kadaluwarsa geus kaliwat.",
+       "protect-unchain-permissions": "Aktifkeun opsi panangtayungan lanjutan",
        "protect-text": "Di dieu anjeun bisa nempo sarta ngarobah hambalan pangonci pikeun kaca '''$1'''.",
        "protect-locked-blocked": "Anjeun teu bisa ngarobah hambalan koncian sabab keur dipeungpeuk. Setélan ayeuna pikeun kaca '''$1''' nyaéta:",
        "protect-locked-access": "Akun anjeun teu wenang ngarobah hambalan pangonci kaca.\nSetélan kiwari pikeun kaca <strong>$1</strong>:",
        "protect-default": "Bisa ku sakabeh pamaké",
        "protect-fallback": "Ngan pikeun pamaké kalawan idin \"$1\"",
        "protect-level-autoconfirmed": "Peungpeuk pamaké anyar jeung nu teu daptar",
-       "protect-level-sysop": "Ngan bisa ku kuncén",
+       "protect-level-sysop": "Ngan pikeun kuncén",
        "protect-summary-cascade": "ngaruntuykeun",
        "protect-expiring": "kadaluwarsa $1",
        "protect-expiring-local": "kadaluwarsa $1",
        "undelete-search-title": "Paluruh kaca kahapus",
        "undelete-search-box": "Téang kaca nu dihapus",
        "undelete-search-prefix": "Témbongkeun kaca dimimitian ku",
+       "undelete-search-full": "Témbongkeun judul kaca nu ngandung:",
        "undelete-search-submit": "Téang",
        "undelete-no-results": "Euweuh kaca nu cocog dina arsip hapusan.",
        "undelete-filename-mismatch": "Hanteu bisa ngabolaykeun hapusan révisi berkas titimangsa $1: ngaran berkas teu cocog.",
        "ipb-unblock-addr": "Buka peungpeuk $1",
        "ipb-unblock": "Nyabut peungpeuk pamaké atawa alamat IP",
        "ipb-blocklist": "Tempo peungpeuk nu diteurapkeun",
-       "ipb-blocklist-contribs": "Ilubiung $1",
+       "ipb-blocklist-contribs": "Kontribusi pikeun {{GENDER:$1|$1}}",
        "ipb-blocklist-duration-left": "Nyésa $1",
        "unblockip": "Buka peungpeuk pamaké",
        "unblockiptext": "Paké formulir di handap pikeun mulangkeun aksés nulis ka alamat IP atawa ngaran pamaké nu saméméhna dipeungpeuk.",
-       "ipusubmit": "Buka peungpeuk pikeun pamaké ieu",
+       "ipusubmit": "Pupus ieu peungpeukan",
        "unblocked": "peungpeuk ka [[User:$1|$1]] geus dicabut",
        "unblocked-range": "$1 geus teu dipeungpeuk",
        "unblocked-id": "peungpeuk $1 geus dicabut",
+       "unblocked-ip": "Pameungpeukan [[Special:Contributions/$1|$1]] geus dicabut.",
        "blocklist": "Kontributor nu dipeungpeuk",
        "autoblocklist": "Otomatis peungpeuk",
        "autoblocklist-submit": "Paluruh",
        "autoblocklist-legend": "Daptar otomatis peungpeuk",
        "autoblocklist-localblocks": "{{PLURAL:$1|Otomatis peungpeuk}} lokal",
+       "autoblocklist-total-autoblocks": "Total peungeukan otomatis: $1",
+       "autoblocklist-empty": "Daptar peungpeukan otomatis kosong.",
+       "autoblocklist-otherblocks": "{{PLURAL:$1|Sapeungpeukan otomatis|Peungpeukan otomatis}} lianna",
        "ipblocklist": "Pamaké nu dipeungpeuk",
        "ipblocklist-legend": "Téang pamaké nu dipeungpeuk",
        "blocklist-userblocks": "Sumputkeun peungpeukan akun",
        "blocklist-tempblocks": "Sumputkeun peungpeukan saheulaanan",
+       "blocklist-addressblocks": "Sumputkeun pameungpeukan IP tunggal",
        "blocklist-timestamp": "Cap titimangsa",
        "blocklist-target": "Udagan",
        "blocklist-expiry": "Kadaluwarsa",
        "block-log-flags-noautoblock": "meungpeuk otomatis dipaéhan",
        "block-log-flags-noemail": "surélek di peungpeuk",
        "block-log-flags-nousertalk": "teu bisa ngédit kaca obrolan sorangan",
+       "block-log-flags-angry-autoblock": "ronjatan sistim pameungpeukan otomatis geus dihurungkeun",
        "block-log-flags-hiddenname": "sandiasma disumputkeun",
        "range_block_disabled": "Pangabisa kuncén pikeun nyieun sarupaning peungpeuk geus ditumpurkeun.",
        "ipb_expiry_invalid": "Wanci daluwarsa teu bener.",
+       "ipb_expiry_old": "Waktu kadaluwarsana mah baheula.",
        "ipb_already_blocked": "\"$1\" geus dipeungpeuk",
        "ipb-needreblock": "$1 geus dipeungpeuk. Rék dirobah sétinganana?",
        "ipb-otherblocks-header": "{{PLURAL:$1|Peungpeuk|Peungpeuk}} lianna",
        "unblock-hideuser": "Anjeun teu bisa muka peungpeuk ieu pamaké, kusabab landihanan keur disumputkeun.",
        "ipb_cant_unblock": "Éror: ID peungpeuk $1 teu kapanggih. Sigana mah geus dibuka.",
        "ip_range_invalid": "Angka IP teu bener.",
+       "ip_range_toolarge": "Panteng blok leuwih badag tibatan /$1 teu diheugbaékeun.",
        "proxyblocker": "Pameungpeuk proxy",
        "proxyblockreason": "Alamat IP anjeun dipeungpeuk sabab mangrupa proxy muka. Mangga tepungan ''Internet service provider'' atanapi ''tech support'' anjeun, béjakeun masalah serius ieu.",
        "sorbsreason": "Alamat IP anjeun kadaptar salaku ''open proxy'' dina DNSBL anu dipaké ku {{SITENAME}}.",
        "sorbs_create_account_reason": "Alamat IP anjeun kadaptar salaku ''open proxy'' dina DNSBL. Anjeun teu bisa nyieun akun",
        "ipbblocked": "Anjeun teu bisa meungpeuk atawa muka peungpeuk séjén kontributor ku sabab anjeun sorangan keur dipeungpeuk",
+       "ipbnounblockself": "Anjeun teu diidinan pikeun muka peungpeukan sorangan.",
        "lockdb": "Konci pangkalan data",
        "unlockdb": "Buka konci pangkalan data",
        "lockdbtext": "Ngonci gudang data bakal numpurkeun kabisa sakabéh pamaké pikeun ngédit kaca, ngarobah préferénsina, ngédit awaskeuneunana, sarta hal séjén nu merlukeun parobahan na gudang data. Konfirmasikeun yén ieu nu dimaksud ku anjeun, sarta anjeun bakal muka konci gudang data nalika pangropéa anjeun geus réngsé.",
        "movenologintext": "Anjeun kudu jadi pamaké nu kadaptar tur [[Special:UserLogin|asup log]] pikeun mindahkeun kaca.",
        "movenotallowed": "Anjeung teu boga kawenangan mindahkeun kaca.",
        "movenotallowedfile": "Anjeung teu boga kawenangan mindahkeun kaca.",
+       "cant-move-user-page": "Anjeun teu miboga hak aksés pikeun ngalihkeun kaca pamaké (misah ti subkaca).",
+       "cant-move-to-user-page": "Anjeun teu miboga hak aksés pikeun ngalihkeun kaca ka hiji kaca pamaké (iwal ka subkaca pamaké).",
+       "cant-move-category-page": "Anjeun teu miboga hak aksés pikeun ngalihkeun kaca kategori.",
+       "cant-move-to-category-page": "Anjeun teu miboga hak aksés pikeun ngalihkeun kaca ka kaca kategori.",
+       "cant-move-subpages": "Anjeun teu miboga hak aksés pikeun ngalihkeun subkaca.",
+       "namespace-nosubpages": "Ngaranspasi \"$1\" teu ngidinan subkaca.",
        "newtitle": "Judul anyar:",
        "move-watch": "Awaskeuneun kaca ieu",
        "movepagebtn": "Pindahkeun kaca",
        "pagemovedsub": "Mindahkeun geus hasil!",
        "movepage-moved": "'''\"$1\" geus dipindahkeun ka \"$2\"'''",
        "movepage-moved-redirect": "Alihan geus dijieunkeun.",
+       "movepage-moved-noredirect": "Panyieunan alihan geus diteken.",
        "articleexists": "Kaca nu ngaranna kitu geus aya, atawa ngaran nu dipilih ku anjeun teu sah. Mangga pilih ngaran séjén.",
        "cantmove-titleprotected": "Anjeun teu bisa mindahkeun kaca ka dieu, sabab éta judul dikonci",
        "movetalk": "Mun bisa, kaca \"obrolan\" ogé pindahkeun.",
        "immobile-source-page": "Ieu kaca teu bisa dipindahkeun.",
        "immobile-target-page": "Teu bisa mindahkeun ka judul nu ditujul.",
        "imagenocrossnamespace": "Teu bisa mindahkeun gambar ka rohangan ngaran nu lain gambar",
+       "nonfile-cannot-move-to-file": "Teu bisa ngalihkeun non-berkas ka ngaranspasi",
        "imagetypemismatch": "Éksténsi berkas anyar teu cocog jeung tipena",
        "imageinvalidfilename": "Ngaran berkas tujuan teu sah",
        "fix-double-redirects": "Hadéan sakabéh alihan ganda nu mungkin kajadian",
        "export-submit": "Ékspor",
        "export-addcattext": "Tambahkeun kaca tina kategori:",
        "export-addcat": "Tambahkeun",
+       "export-addnstext": "Tambahkeun kaca ti ngaranspasi:",
        "export-addns": "Tambahkeun",
        "export-download": "Simpen salaku berkas",
        "export-templates": "Kaasup citakan",
+       "export-pagelinks": "Sartakeun kaca patali nepi ka kajeroan:",
        "export-manual": "Tambahkeun kaca manual:",
        "allmessages": "Talatah sistim",
        "allmessagesname": "Ngaran",
        "allmessagestext": "Ieu mangrupa daptar talatah sistim nu aya na spasi ngaran MediaWiki:.",
        "allmessagesnotsupportedDB": "Kaca ieu teu dirojong sabab '''$wgUseDatabaseMessages''' pareum.",
        "allmessages-filter-legend": "Ayakan",
+       "allmessages-filter": "Saring ku kaayaan kustomisasi:",
        "allmessages-filter-unmodified": "Teu diropéa",
        "allmessages-filter-all": "Kabéh",
        "allmessages-filter-modified": "Diropéa",
        "thumbnail-more": "Gedéan",
        "filemissing": "Berkas leungit",
        "thumbnail_error": "Kasalahan sawaktu nyieun gambar leutik (thumbnail): $1",
+       "thumbnail_error_remote": "Talatah kasalahan ti $1:\n$2",
        "djvu_page_error": "Kaca DjVu teu kawadahan",
        "djvu_no_xml": "Hanteu bisa nyokot XML pikeun berkas DjVu",
+       "thumbnail-temp-create": "Teu bisa nyieun berkas leutik témporer",
        "thumbnail_invalid_params": "Kasalahan paraméter miniatur",
+       "thumbnail_toobigimagearea": "Berkas kalawan diménsi leuwih badag batan $1",
        "thumbnail_dest_directory": "Diréktori nu dituju teu bisa dijieun",
-       "thumbnail_image-type": "Jenis gambar teu dirojong",
+       "thumbnail_image-type": "Jinis gambar teu dirojong",
        "thumbnail_image-missing": "Berkas anu sigana leungit: $1",
        "import": "Impor kaca",
        "importinterwiki": "Impor tina wiki lianna",
        "import-interwiki-history": "Téplak sakabéh vérsi jujutan pikeun ieu kaca",
        "import-interwiki-templates": "Kaasup sakabéh citakan",
        "import-interwiki-submit": "Impor",
+       "import-mapping-default": "Impor ka lokasi standar",
+       "import-mapping-namespace": "Impor ka ngaranspasi:",
        "import-upload-filename": "Ngaran berkas:",
        "import-comment": "Ringkesan:",
        "importtext": "Mangga ékspor koropakna ti sumber nu dipaké ku wiki migunakeun fungsi Special:Export, simpen na piringan anjeun, teras muatkeun di dieu.",
        "importuploaderrortemp": "Koropak impor gagal dimuat. Folder samentarana leungit.",
        "import-parse-failure": "Prosés impor XML teu hasil",
        "import-noarticle": "Euweuh kaca imporeun!",
-       "import-nonewrevisions": "Sakabéh révisi geus kungsi diimpor saméméhna.",
+       "import-nonewrevisions": "Euweuh révisi nu diimpor (sakum révisi geus aya atawa diliwatan lantaran kasalahan).",
        "xml-error-string": "$1 dina baris $2, kolom $3 (bit $4): $5",
        "import-upload": "Ngamuat data XML",
        "import-invalid-interwiki": "Teu bisa ngimpor ti wiki nu dipilih.",
        "import-rootpage-invalid": "Kaca turunan nu dibikeun judulna salah.",
        "importlogpage": "Log impor",
        "importlogpagetext": "Impor administratif kaca-kaca ti wiki séjén katut jujutanana.",
-       "import-logentry-upload-detail": "$1 {{PLURAL:$1|vérsi heubeul}}",
-       "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|vérsi heubel}} ti $2",
+       "import-logentry-upload-detail": "$1 {{PLURAL:$1|sarévisi|révisi}} diimpor",
+       "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|sarévisi|révisi}} diimpor ti $2",
        "javascripttest": "Nguji JavaScript",
        "javascripttest-pagetext-unknownaction": "Tarékah \"$1\" teu dipikanyaho.",
        "tooltip-pt-userpage": "Kaca {{GENDER:|pamaké anjeun}}",
        "tooltip-pt-preferences": "Préferénsi {{GENDER:|anjeun}}",
        "tooltip-pt-watchlist": "Daptar kaca nu diawaskeun ku anjeun parobahanana.",
        "tooltip-pt-mycontris": "Béréndélan kontribusi {{GENDER:|anjeun}}",
+       "tooltip-pt-anoncontribs": "Daptar éditan nu dijieun ti ieu alamat IP",
        "tooltip-pt-login": "Leuwih hadé asup log, sok sanajan teu wajib",
        "tooltip-pt-login-private": "Anjeun perlu asup log pikeun migunakeun ieu wiki",
        "tooltip-pt-logout": "Kaluar log",
        "tooltip-feed-atom": "Asupan atom pikeun kaca ieu",
        "tooltip-t-contributions": "Béréndélan kontribusi ti {{GENDER:$1|ieu pamaké}}",
        "tooltip-t-emailuser": "Kirim surélék ka {{GENDER:$1|ieu kontributor}}",
+       "tooltip-t-info": "Informasi leuwih jero ngeunaan ieu kaca",
        "tooltip-t-upload": "Unjal berkas",
        "tooltip-t-specialpages": "Daptar sadaya kaca husus",
        "tooltip-t-print": "Vérsi citakeun ieu kaca",
        "tooltip-watch": "Tambahkeun kaca ieu kana awaskeuneun kuring",
        "tooltip-watchlistedit-normal-submit": "Pupus judul",
        "tooltip-watchlistedit-raw-submit": "Anyarkeun aawaseun",
+       "tooltip-recreate": "Jieun deui kaca sanajan sabenerna mah geus dipupus",
        "tooltip-upload": "Prung unjalkeun",
        "tooltip-rollback": "Mulangkeun éditan ka panulis panungtung dina sakali klik",
        "tooltip-undo": "\"Bolay\" malikkeun ieu éditan sarta muka kotak édit dina modeu pramidang.\nCara kieu bisa nambahkeun alesan dina ringkesanana.",
        "lastmodifiedatby": "Kaca ieu panungtungan diédit $2, $1 ku $3.",
        "othercontribs": "Dumasar karya $1.",
        "others": "Séjénna",
-       "siteusers": "{{PLURAL:$2|Pamaké|Pamaké-pamaké}} {{SITENAME}} $1",
+       "siteusers": "{{PLURAL:$2|pamaké|para pamaké}} {{SITENAME}} $1",
+       "anonusers": "{{PLURAL:$2|pamaké|para pamaké}} anonim {{SITENAME}} $1",
        "creditspage": "Pangajén kaca",
        "nocredits": "Teu aya émbaran pangajén pikeun kaca ieu.",
        "spamprotectiontitle": "Saringan spam",
        "spamprotectiontext": "Kaca anu rék disimpen dipeungpeuk ku saringan spam.\nSigana mah ieu téh alatan tutumbu ka loka luar anu dibléklis.",
+       "spamprotectionmatch": "Teks di handap ieu mancing saringan spam kami: $1",
+       "spambot_username": "Pamersihan spam MediaWiki",
        "simpleantispam-label": "Pamariksaan anti-spam.\nAnu ieu <strong>ulah</strong> dieusian!",
        "pageinfo-title": "Émbaran pikeun \"$1\"",
        "pageinfo-header-basic": "Émbaran dasar",
        "pageinfo-header-restrictions": "Protéksi kaca",
        "pageinfo-header-properties": "Properti kaca",
        "pageinfo-display-title": "Judul pidangan",
+       "pageinfo-default-sort": "Konci susun baku",
        "pageinfo-length": "Panjang kaca (dina bit)",
        "pageinfo-article-id": "ID kaca",
        "pageinfo-language": "Basa eusi kaca",
        "pageinfo-robot-index": "Digaékeun",
        "pageinfo-robot-noindex": "Dicaram",
        "pageinfo-watchers": "Jumlah paroris kaca",
+       "pageinfo-visiting-watchers": "Jumlah pamariksa kaca nu nyorang éditan kiwari",
        "pageinfo-few-watchers": "Kurang ti $1 {{PLURAL:$1|pangawas}}",
+       "pageinfo-few-visiting-watchers": "Rék aya atawa henteuna pamariksa kaca nu nyorang éditan kiwari",
        "pageinfo-redirects-name": "Jumlah pindahan ka ieu kaca",
+       "pageinfo-subpages-name": "Nomor subkaca ieu kaca",
        "pageinfo-firstuser": "Panyieun kaca",
+       "pageinfo-firsttime": "Tanggal panyieunan kaca",
        "pageinfo-lastuser": "Pangédit panungtung",
+       "pageinfo-lasttime": "Tanggal éditan panungtung",
        "pageinfo-edits": "Jumlah éditan",
        "pageinfo-authors": "Jumlah kontributor nu béda",
+       "pageinfo-recent-edits": "Jumlah éditan kiwari (dina $1 panungtung)",
        "pageinfo-toolboxlink": "Émbaran kaca",
        "pageinfo-redirectsto": "Alihkeun ka",
        "pageinfo-redirectsto-info": "info",
        "filedelete-current-unregistered": "Koropak \"$1\" euweuh dina pangkalan data.",
        "previousdiff": "← Éditan saméméhna",
        "nextdiff": "Éditan salajengna →",
-       "imagemaxsize": "Watesan gambar na kaca dadaran gambar nepi ka:",
+       "imagemaxsize": "Wates ukuran gambar:<br />''(pikeun kaca déskripsi berkas)''",
        "thumbsize": "Ukuran miniatur:",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|kaca|kaca}}",
        "file-info": "ukuran koropak: $1, tipeu MIME: $2",
        "newimages-legend": "Saringan",
        "newimages-label": "Ngaran berkas (atawa sawaréh tina ngaranna):",
        "newimages-user": "Alamat IP atawa sandiasma",
+       "newimages-newbies": "Témbongkeun kontribusi ti akun anyar wungkul",
+       "newimages-showbots": "Témbongkeun unjalan ku bot",
+       "newimages-hidepatrolled": "Sumputkeun unjalan nu geus diriksa",
+       "newimages-mediatype": "Tipeu média:",
        "noimages": "Taya nanaon.",
        "gallery-slideshow-toggle": "''Toggle'' miniatur",
        "ilsubmit": "Paluruh",
        "exif-orientation": "Oriéntasi",
        "exif-samplesperpixel": "Jumlah komponén",
        "exif-planarconfiguration": "Susunan data",
+       "exif-ycbcrsubsampling": "Rasio subsampling Y ka C",
        "exif-ycbcrpositioning": "Perenah Y jeung C",
        "exif-xresolution": "Résolusi horizontal",
        "exif-yresolution": "Résolusi tangtung",
        "exif-stripoffsets": "Perenah data gambar",
+       "exif-rowsperstrip": "Jumlah baris per strip",
+       "exif-stripbytecounts": "Bita per strip komprési",
        "exif-jpeginterchangeformat": "Ofset ka JPEG SOI",
        "exif-jpeginterchangeformatlength": "Bit data JPEG",
+       "exif-whitepoint": "Kromatisitas titik bodas",
+       "exif-primarychromaticities": "Kromatisitas warna primér",
+       "exif-ycbcrcoefficients": "Koéfisién matriks transformasi rohang warna",
        "exif-referenceblackwhite": "Pasangan ajen rujukan hideung jeung bodas",
        "exif-datetime": "Wanci jeung titimangsa parobahan koropak",
        "exif-imagedescription": "Judul gambar",
        "exif-artist": "Pangarang",
        "exif-copyright": "Nu nyepeng hak cipta",
        "exif-exifversion": "Vérsi Exif",
+       "exif-flashpixversion": "Pangrojong vérsi Flashpix",
        "exif-colorspace": "Rohangan warna",
        "exif-componentsconfiguration": "Harti unggak komponén",
        "exif-compressedbitsperpixel": "Mode komprési gambar",
        "exif-pixelxdimension": "Lébar gambar",
        "exif-pixelydimension": "Jangkung gambar",
        "exif-usercomment": "Koméntar pamaké",
+       "exif-relatedsoundfile": "Berkas audio nu patali",
        "exif-datetimeoriginal": "Titimangsa jeung wanci dijieunna data",
        "exif-datetimedigitized": "Titimangsa jeung wanci digitisasi",
+       "exif-subsectime": "Subdetik DateTime",
+       "exif-subsectimeoriginal": "Subdetik DateTimeOriginal",
+       "exif-subsectimedigitized": "Subdetik DateTimeDigitized",
        "exif-exposuretime": "Waktu pajanan",
        "exif-exposuretime-format": "$1 detik ($2)",
        "exif-fnumber": "Nomer F",
        "exif-whitebalance": "Kasaimbangan bodas",
        "exif-digitalzoomratio": "Rasio zum digital",
        "exif-focallengthin35mmfilm": "Panjang fokus dina film 35 mm",
+       "exif-scenecapturetype": "Tipeu panéwakan",
+       "exif-gaincontrol": "Kontrol layar",
        "exif-contrast": "Kontras",
        "exif-saturation": "Saturasi",
        "exif-sharpness": "Seukeutna",
+       "exif-devicesettingdescription": "Déskripsi pangaturan alat",
+       "exif-subjectdistancerange": "Lolongkrang jarak subyék",
        "exif-imageuniqueid": "ID unik gambar",
        "exif-gpsversionid": "Vérsi tag GPS",
        "exif-gpslatituderef": "Gurat Kalér atawa Kidul",
        "exif-gpslatitude": "Gurat Lintang",
        "exif-gpslongituderef": "Gurat Wétan atawa Kulon",
        "exif-gpslongitude": "Gurat Bujur",
+       "exif-gpsaltituderef": "Référénsi jangkung",
+       "exif-gpsaltitude": "Jangkung",
        "exif-gpstimestamp": "Wanci GPS (jam atomik)",
+       "exif-gpssatellites": "Satelit pikeun pangukuran",
        "exif-gpsstatus": "Status panampa",
+       "exif-gpsmeasuremode": "Modeu pangukuran",
+       "exif-gpsdop": "Katepatan pangukuran",
        "exif-gpsspeedref": "Unit kecepatan",
+       "exif-gpsspeed": "Gancangna panarima GPS",
+       "exif-gpstrackref": "Référénsi arah obahan",
+       "exif-gpstrack": "Arah obahan",
+       "exif-gpsimgdirectionref": "Référénsi arah gambar",
+       "exif-gpsimgdirection": "Arah gambar",
+       "exif-gpsmapdatum": "Data survéi géodési",
+       "exif-gpsdestlatituderef": "Référénsi lintang ti tujuan",
+       "exif-gpsdestlatitude": "Lintang tujuan",
+       "exif-gpsdestlongituderef": "Référénsi bujur ti tujuan",
+       "exif-gpsdestlongitude": "Bujur tujuan",
+       "exif-gpsdestbearingref": "Référénsi bearing tujuan",
+       "exif-gpsdestbearing": "Bearing tujuan",
+       "exif-gpsdestdistanceref": "Référénsi jarak ti tujuan",
+       "exif-gpsdestdistance": "Jarak ti tujuan",
        "exif-gpsprocessingmethod": "Ngaran métodeu olah GPS",
        "exif-gpsareainformation": "Ngaran wewengkon GPS",
        "exif-gpsdatestamp": "Titimangsa GPS",
        "exif-gpsdifferential": "Koréksi diferensial GPS",
+       "exif-jpegfilecomment": "Koméntar berkas JPEG",
        "exif-keywords": "Kecap konci",
        "exif-worldregioncreated": "Wewengkon dunya tempat moto",
        "exif-countrycreated": "Nagara tempat moto",
        "exif-sublocationdest": "Sublokasi kota nu ditémbongkeun",
        "exif-objectname": "Judul pondok",
        "exif-specialinstructions": "Paréntah husus",
+       "exif-headline": "Lulugu",
+       "exif-credit": "Krédit/Panyadia",
        "exif-source": "Sumber",
        "exif-editstatus": "Status éditorial gambar",
        "exif-urgency": "Urgensi",
+       "exif-fixtureidentifier": "Ngaran fikstur",
        "exif-locationdest": "Lokasi nu digambarkeun",
        "exif-locationdestcode": "Kodeu lokasi nu digambarkeun",
        "exif-writer": "Nu nulis",
        "exif-disclaimer": "Bantahan",
        "exif-contentwarning": "Pépéling eusi",
        "exif-giffilecomment": "Koméntar berkas GIF",
-       "exif-intellectualgenre": "Jenis objék",
+       "exif-intellectualgenre": "Jinis objék",
        "exif-subjectnewscode": "Kodeu subjék",
        "exif-scenecode": "Sandi adegan IPTC",
        "exif-event": "Kajaadian anu ditémbongkan",
        "exif-orientation-6": "Diputer 90° CCW",
        "exif-orientation-7": "Diputer 90° CW jeung dibalikkeun vértikal",
        "exif-orientation-8": "Diputer 90° CW",
+       "exif-planarconfiguration-1": "format chunky",
        "exif-planarconfiguration-2": "format datar",
+       "exif-colorspace-65535": "Teu dikalibrasi",
        "exif-componentsconfiguration-0": "euweuh",
+       "exif-exposureprogram-0": "Teu kadéfinisi",
        "exif-exposureprogram-1": "Manual",
        "exif-exposureprogram-2": "Program normal",
+       "exif-exposureprogram-3": "Prioritas bukaan",
+       "exif-exposureprogram-4": "Prioritas panutup",
+       "exif-exposureprogram-5": "Program kréatif (condong ka jejeroan ruang)",
+       "exif-exposureprogram-6": "Program aksi (condong ka gagancangna rana)",
        "exif-exposureprogram-7": "Modeu potrét (pikeun poto deukeut nu tukangna di luar fokus)",
        "exif-exposureprogram-8": "Modeu Lanskap (pikeun poto lanskap nu tukangna asup fokus)",
        "exif-subjectdistance-value": "$1 méter",
        "exif-gpsdirection-t": "Arah sajati",
        "exif-gpsdirection-m": "Arah magnétik",
        "exif-ycbcrpositioning-1": "Nengah",
+       "exif-ycbcrpositioning-2": "Atas (co-sited)",
        "exif-dc-contributor": "Kontributor",
        "exif-dc-date": "Titimangsa",
        "exif-dc-publisher": "Pamedal",
        "exif-dc-relation": "Média anu tumali",
        "exif-dc-rights": "Hak",
        "exif-dc-source": "Média sumber",
-       "exif-dc-type": "Jenis média",
+       "exif-dc-type": "Jinis média",
        "exif-rating-rejected": "Ditolak",
        "exif-isospeedratings-overflow": "Leuwih ti 65535",
        "exif-iimcategory-ace": "Seni, budaya, jeung hiburan",
        "exif-iimcategory-clj": "Hukum jeung kajahatan",
        "exif-iimcategory-dis": "Bencana jeung kacilakaan",
+       "exif-iimcategory-fin": "Ékonomi jeung bisnis",
        "exif-iimcategory-edu": "Atikan",
        "exif-iimcategory-evn": "Lingkungan",
        "exif-iimcategory-hth": "Kawaluyaan",
+       "exif-iimcategory-hum": "Minat jalma",
        "exif-iimcategory-lab": "Katanagakerjaan",
        "exif-iimcategory-lif": "Gaya hirup jeung rékréasi",
        "exif-iimcategory-pol": "Politik",
        "exif-iimcategory-sci": "Sayen jeung téknologi",
        "exif-iimcategory-soi": "Isu sosial",
        "exif-iimcategory-spo": "Olahraga",
+       "exif-iimcategory-war": "Perang, konflik, jeung karesahan",
        "exif-iimcategory-wea": "Cuaca",
        "exif-urgency-normal": "Normal ($1)",
        "exif-urgency-low": "Landeuh ($1)",
        "version-specialpages": "Kaca husus",
        "version-parserhooks": "Kait parser",
        "version-variables": "Variabel",
+       "version-antispam": "Panyegahan spam",
        "version-other": "Séjén",
+       "version-mediahandlers": "Pananganan média",
        "version-hooks": "Kait",
        "version-parser-extensiontags": "Tag éksténsi parser",
+       "version-parser-function-hooks": "Kait fungsi parser",
        "version-hook-name": "Ngaran kait",
        "version-hook-subscribedby": "Didaptarkeun ku",
        "version-version": "($1)",
+       "version-no-ext-name": "[tanpa ngaran]",
        "version-license": "Lisénsi MediaWiki",
        "version-ext-license": "Lisénsi",
        "version-ext-colheader-name": "Éksténsi",
        "version-software": "Sopwér nu geus diinstal",
        "version-software-product": "Produk",
        "version-software-version": "Vérsi",
+       "version-entrypoints": "URL titik éntri",
+       "version-entrypoints-header-entrypoint": "Titik éntri",
        "version-entrypoints-header-url": "URL",
        "version-libraries": "Pabukon kapasang",
        "version-libraries-library": "Pabukon",
        "fileduplicatesearch-info": "$1 × $2 piksel<br />Ukuran koropak: $3<br />Tipeu MIME: $4",
        "fileduplicatesearch-result-1": "Koropak \"$1\" teu boga duplikat idéntik.",
        "fileduplicatesearch-result-n": "Koropak \"$1\" mibanda {{PLURAL:$2|1 duplikat idéntik|$2 duplikat idéntik}}.",
+       "fileduplicatesearch-noresults": "Teu manggihan berkas nu ngaranna \"$1\".",
        "specialpages": "Kaca husus",
        "specialpages-note-top": "Kamandang",
        "specialpages-group-maintenance": "Laporan pigawéeun",
        "tags-tag": "Ngaran tag",
        "tags-source-header": "Sumber",
        "tags-active-header": "Hurung?",
+       "tags-hitcount-header": "Parobahan kalawan tag",
        "tags-actions-header": "Tarékah",
        "tags-active-yes": "Enya",
        "tags-active-no": "Teu",
+       "tags-source-extension": "Ditangtukeun ku pakakas lemes",
+       "tags-source-none": "Teu dipaké deui",
        "tags-edit": "édit",
+       "tags-delete": "pupus",
        "tags-activate": "hurungkeun",
        "tags-deactivate": "pareuman",
        "tags-hitcount": "$1 {{PLURAL:$1|parobahan|parobahan}}",
        "tags-create-tag-name": "Ngaran tag:",
        "tags-create-reason": "Alesan:",
        "tags-create-submit": "Jieun",
+       "tags-create-already-exists": "Tag \"$1\" geus aya.",
+       "tags-create-warnings-below": "Anjeun rék nuluykeun panyieunan ieu tag?",
        "tags-delete-title": "Pupus tag",
        "tags-delete-reason": "Alesan:",
        "tags-activate-title": "Hurungkeun tag",
        "tags-deactivate-reason": "Alesan:",
        "tags-deactivate-submit": "Pareuman",
        "tags-edit-title": "Édit tag",
+       "tags-edit-existing-tags": "Tag nu aya:",
        "tags-edit-existing-tags-none": "<em>Taya</em>",
        "tags-edit-new-tags": "Tag anyar:",
+       "tags-edit-add": "Tambahkeun ieu tag-tag:",
        "tags-edit-remove": "Pupus ieu tag-tag:",
+       "tags-edit-remove-all-tags": "(pupus sakum tag)",
+       "tags-edit-chosen-placeholder": "Pilih sawatara tag",
+       "tags-edit-chosen-no-results": "Euweuh tag nu luyu",
        "tags-edit-reason": "Alesan:",
+       "tags-edit-revision-submit": "Larapkeun parobahan kana {{PLURAL:$1|ieu révisi|$1 révisi}}",
        "comparepages": "Bandinkeun kaca",
        "compare-page1": "Kaca 1",
        "compare-page2": "Kaca 2",
        "compare-rev1": "Révisi 1",
        "compare-rev2": "Révisi 2",
        "compare-submit": "Bandingkeun",
+       "diff-form": "Béda",
+       "diff-form-oldid": "ID révisi heubeul (opsional)",
+       "diff-form-submit": "Témbongkeun bédana",
+       "permanentlink": "Tutumbu permanén",
+       "permanentlink-revid": "ID révisi",
+       "permanentlink-submit": "Tojo ka révisi",
        "dberr-problems": "Punten! Nuju aya gangguan téhnis.",
        "dberr-again": "Cobi antos sababaraha menit, lajeng dimuat ulang.",
        "dberr-info": "(Teu bisa ngaksés basis data: $1)",
+       "dberr-info-hidden": "(Teu bisa ngaksés basis data)",
        "dberr-usegoogle": "Kanggo samentawis, tiasa dicobi milari di Google.",
        "htmlform-select-badoption": "Niléy anu diasupkeun teu bener.",
        "htmlform-float-invalid": "Niléy anu diasupkeun lain angka.",
        "htmlform-no": "Henteu",
        "htmlform-yes": "Enya",
        "htmlform-chosen-placeholder": "Pilih opsi",
+       "htmlform-cloner-create": "Tambahkeun leuwih loba",
        "htmlform-cloner-delete": "Pupus",
+       "htmlform-cloner-required": "Saeutikna hiji niléy diperlukeun.",
        "htmlform-date-placeholder": "TTTT-BB-HH",
        "htmlform-time-placeholder": "JJ:MM:DD",
        "htmlform-datetime-placeholder": "TTTT-BB-HH JJ:MM:DD",
        "logentry-newusers-create": "Akun pamaké $1 geus {{GENDER:$2|dijieun}}",
        "logentry-newusers-autocreate": "Akun pamaké $1 {{GENDER:$2|dijieun}} otomatis",
        "logentry-upload-upload": "$1 {{GENDER:$2|ngamuat}} $3",
+       "log-name-tag": "Log tag",
        "rightsnone": "(euweuh)",
+       "feedback-adding": "Nambahkeun eupan balik kana kaca...",
        "feedback-back": "Balik deui",
        "feedback-cancel": "Bolay",
        "feedback-close": "Anggeus",
        "duration-decades": "$1 {{PLURAL:$1|dékadeu|dékadeu}}",
        "duration-centuries": "$1 {{PLURAL:$1|abad|abad}}",
        "duration-millennia": "$1 {{PLURAL:$1|milénium|milénium}}",
+       "limitreport-walltime": "Pamakéan waktu nyaan",
        "expandtemplates": "Mekarkeun citakan",
        "expand_templates_input": "Téks input:",
        "expand_templates_output": "Hasil:",
        "expand_templates_xml_output": "Output XML",
+       "expand_templates_html_output": "Kaluaran HTML atah",
        "expand_templates_ok": "Heug",
        "expand_templates_remove_comments": "Pupus koméntar",
+       "expand_templates_generate_xml": "Témbongkeun tangkal parser XML",
+       "expand_templates_generate_rawhtml": "Témbongkeun HTML atah",
        "expand_templates_preview": "Pramidang",
+       "pagelanguage": "Robah basa kaca",
        "pagelang-name": "Kaca",
        "pagelang-language": "Basa",
+       "pagelang-use-default": "Paké basa baku",
        "pagelang-select-lang": "Pilih basa",
        "pagelang-reason": "Alesan",
        "pagelang-submit": "Kirim",
        "pagelang-nonexistent-page": "Kaca $1 euweuh.",
+       "right-pagelang": "Robah basa kaca",
+       "action-pagelang": "ngarobah basa kaca",
+       "log-name-pagelang": "Log parobahan basa",
        "mediastatistics": "Statistik média",
        "mediastatistics-table-mimetype": "Tipeu MIME",
+       "mediastatistics-table-extensions": "Éksténsi ngamumkinkeun",
        "mediastatistics-table-count": "Jumlah berkas",
        "mediastatistics-table-totalbytes": "Ukuran gabungan",
        "mediastatistics-header-unknown": "Teu dipikanyaho",
        "mediastatistics-header-audio": "Audio",
        "mediastatistics-header-video": "Vidio",
        "mediastatistics-header-multimedia": "Média beunghar",
+       "mediastatistics-header-office": "Aplikasi Office",
        "mediastatistics-header-text": "Tékstual",
+       "mediastatistics-header-executable": "Program",
+       "mediastatistics-header-archive": "Format komprési",
        "mediastatistics-header-total": "Sakumna berkas",
+       "json-error-state-mismatch": "JSON teu sah atawa cacat",
+       "json-error-syntax": "Kasalahan sintaks",
+       "headline-anchor-title": "Tutumbu ka bagéan ieu",
        "special-characters-group-latin": "Latin",
+       "special-characters-group-latinextended": "Éksténsi Latin",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Lambang",
        "special-characters-group-greek": "Yunani",
+       "special-characters-group-greekextended": "Éksténsi Yunani",
        "special-characters-group-cyrillic": "Sirilik",
        "special-characters-group-arabic": "Arab",
+       "special-characters-group-arabicextended": "Éksténsi Arab",
        "special-characters-group-persian": "Parsi",
        "special-characters-group-hebrew": "Ibrani",
        "special-characters-group-bangla": "Bangla",
+       "special-characters-group-tamil": "Tamil",
        "special-characters-group-telugu": "Telugu",
        "special-characters-group-sinhala": "Sinhala",
        "special-characters-group-gujarati": "Gujarati",
+       "special-characters-group-devanagari": "Dewanagari",
        "special-characters-group-thai": "Thai",
        "special-characters-group-lao": "Lao",
        "special-characters-group-khmer": "Khmer",
+       "special-characters-group-canadianaboriginal": "Aborigin Kanada",
+       "special-characters-title-endash": "gurat en",
+       "special-characters-title-emdash": "gurat em",
+       "special-characters-title-minus": "tanda kurang",
+       "mw-widgets-dateinput-no-date": "Euweuh tanggal pinilih",
+       "mw-widgets-mediasearch-input-placeholder": "Paluruh média",
+       "mw-widgets-mediasearch-noresults": "Euweuh hasil nu kapanggih.",
+       "mw-widgets-titleinput-description-new-page": "kaca can aya",
+       "mw-widgets-titleinput-description-redirect": "ngalihkeun ka $1",
+       "mw-widgets-categoryselector-add-category-placeholder": "Tambahkeun kategori...",
+       "mw-widgets-usersmultiselect-placeholder": "Tambahkeun leuwih loba...",
+       "date-range-from": "Ti ping:",
+       "date-range-to": "Nepi ping:",
+       "sessionprovider-generic": "sési $1",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "sési dumasar kuki",
+       "sessionprovider-nocookies": "Kuki meureun dipareuman. Pastikeun anjeun geus ngahurungkeun kuki sarta coba balikan deui.",
+       "randomrootpage": "Kaca dasar acak",
+       "log-action-filter-block": "Jinis pameungpeukan:",
+       "log-action-filter-contentmodel": "Jinis parobahan modél kontén:",
+       "log-action-filter-delete": "Jinis pamupusan:",
+       "log-action-filter-import": "Jinis impor:",
+       "log-action-filter-managetags": "Jinis tarékah ménéjemén tag:",
+       "log-action-filter-move": "Jinis alihan:",
+       "log-action-filter-newusers": "Jinis panyieunan akun:",
+       "log-action-filter-patrol": "Jinis riksaan:",
+       "log-action-filter-protect": "Jinis panangtayungan:",
+       "log-action-filter-rights": "Jinis parobahan hak aksés:",
+       "log-action-filter-suppress": "Jinis panyamunian:",
+       "log-action-filter-upload": "Jinis unjalan:",
        "log-action-filter-all": "Sakumna",
        "log-action-filter-block-block": "Peungpeuk",
+       "log-action-filter-block-reblock": "Modifikasi peungpeuk",
+       "log-action-filter-block-unblock": "Bolay peungpeukan",
+       "log-action-filter-contentmodel-change": "Robah modél kontén",
+       "log-action-filter-delete-delete": "Pamupusan kaca",
+       "log-action-filter-delete-delete_redir": "Ngalihkeun pangalihan",
+       "log-action-filter-delete-restore": "Bolayan pamupusan",
+       "log-action-filter-delete-event": "Log pamupusan",
+       "log-action-filter-delete-revision": "Pamupusan révisi",
+       "log-action-filter-import-interwiki": "Impor transwiki",
+       "log-action-filter-import-upload": "Impor kalawan unjalan XML",
+       "log-action-filter-managetags-create": "Panyieunan tag",
+       "log-action-filter-managetags-delete": "Pamupusan tag",
+       "log-action-filter-managetags-activate": "Pangaktifan tag",
+       "log-action-filter-managetags-deactivate": "Pamareuman tag",
+       "log-action-filter-move-move": "Alihkeun tanpa ngalandih pangalihan",
+       "log-action-filter-move-move_redir": "Alihkeun kalawan ngalandih pangalihan",
+       "log-action-filter-newusers-create": "Jijieunan ku pamaké anonim",
+       "log-action-filter-newusers-create2": "Jijieunan ku pamaké kadaptar",
+       "log-action-filter-newusers-autocreate": "Panyieunan otomatis",
+       "log-action-filter-newusers-byemail": "Panyieunan kalawan kecap sandi nu dikirimkeun liwat surélék",
+       "log-action-filter-patrol-patrol": "Riksaan manual",
+       "log-action-filter-patrol-autopatrol": "Riksaan otomatis",
+       "log-action-filter-protect-protect": "Panangtayungan",
+       "log-action-filter-protect-modify": "Modifikasi panangtayungan",
+       "log-action-filter-protect-unprotect": "Bolay panangtayungan",
+       "log-action-filter-protect-move_prot": "Pangalihan panangtayungan",
+       "log-action-filter-rights-rights": "Parobahan manual",
+       "log-action-filter-rights-autopromote": "Parobahan otomatis",
+       "log-action-filter-suppress-event": "Log samunian",
+       "log-action-filter-suppress-revision": "Panyamunian révisi",
+       "log-action-filter-suppress-delete": "Panyamunian kaca",
+       "log-action-filter-upload-upload": "Unjalan anyar",
+       "log-action-filter-upload-overwrite": "Unjal deui",
+       "authmanager-create-disabled": "Panyieunan akun ditumpurkeun",
+       "authmanager-authplugin-setpass-failed-title": "Parobahan kecap sandi gagal",
+       "authmanager-authplugin-setpass-bad-domain": "Domain teu sah.",
+       "authmanager-autocreate-noperm": "Panyieunan akun otomatis teu diidinan.",
+       "authmanager-userdoesnotexist": "Akun pamaké \"$1\" teu kadaptar.",
+       "authmanager-username-help": "Sandiasma pikeun oténtikasi.",
+       "authmanager-password-help": "Kecap sandi pikeun oténtikasi.",
+       "authmanager-domain-help": "Domain pikeun oténtikasi éksternal.",
+       "authmanager-retype-help": "Kecap sandi deui pikeun konfirmasi.",
+       "authmanager-email-label": "Surélék",
+       "authmanager-email-help": "Alamat surélék",
+       "authmanager-realname-label": "Ngaran asli",
+       "authmanager-realname-help": "Ngaran asli pamaké",
+       "authmanager-provider-password": "Oténtikasi dumasar kecap sandi",
+       "authprovider-resetpass-skip-label": "Liwatan",
+       "authprovider-resetpass-skip-help": "Liwatan pamulangan kecap sandi",
+       "authform-newtoken": "Token leungit. $1",
+       "authform-notoken": "Token leungit",
+       "authform-wrongtoken": "Token salah",
+       "specialpage-securitylevel-not-allowed-title": "Teu diidinan",
+       "specialpage-securitylevel-not-allowed": "Hampura, anjeun teu diidinan pikeun maké ieu kaca lantaran idéntitas anjeun teu bisa divérifikasi.",
+       "authpage-cannot-login": "Teu bisa ngamimitian asup log.",
+       "authpage-cannot-login-continue": "Teu bisa neruskeun asup log. Sési anjeun jigana geus kadaluwarsa.",
+       "authpage-cannot-create": "Teu bisa ngamimitian nyieun akun.",
+       "authpage-cannot-create-continue": "Teu bisa nuluykeun nyieun akun. Sési anjeun jigana geus kadaluwarsa.",
+       "authpage-cannot-link": "Teu bisa ngamimitian numbukeun akun.",
+       "authpage-cannot-link-continue": "Teu bisa nulurkeun numbukeun akun. Sési anjeun jigana geus kadaluwarsa.",
+       "cannotauth-not-allowed-title": "Idin ditolak",
+       "cannotauth-not-allowed": "Anjeun teu diidinan pikeun maké ieu kaca",
+       "changecredentials": "Robah krédensial",
+       "changecredentials-submit": "Robah krédensial",
        "removecredentials": "Pupus krédensial",
+       "removecredentials-submit": "Pupus krédensial",
        "removecredentials-success": "Krédensial Anda geus dipupus.",
+       "credentialsform-provider": "Jinis krédensial:",
+       "credentialsform-account": "Ngaran akun:",
+       "cannotlink-no-provider-title": "Euweuh akun nu bisa ditumbukeun",
+       "cannotlink-no-provider": "Euweuh akun nu bisa ditumbukeun",
+       "linkaccounts": "Tumbukeun akun",
+       "linkaccounts-success-text": "Akun geus ditumbukeun.",
+       "linkaccounts-submit": "Tumbukeun akun",
+       "unlinkaccounts": "Buka tumbuan akun",
+       "unlinkaccounts-success": "Akun geus teu ditumbukeun",
+       "restrictionsfield-badip": "Alamat IP atawa pantengan IP teu sah: $1",
+       "restrictionsfield-label": "Pantengan IP nu diheugbaékeun:",
+       "restrictionsfield-help": "Hiji alamat IP atawa pantengan CIDR per baris. Pikeun ngahurungkeun sakumna, paké:\n<pre>0.0.0.0/0\n::/0</pre>",
+       "revid": "révisi $1",
+       "pageid": "ID kaca $1",
        "gotointerwiki": "Ninggalkeun{{SITENAME}}",
-       "gotointerwiki-invalid": "Judul nu ditangtukeun teu sah."
+       "gotointerwiki-invalid": "Judul nu ditangtukeun teu sah.",
+       "undelete-cantedit": "Anjeun teu bisa ngabolaykeun pamupusan ieu kaca lantaran anjeun teu bisa ngédit ieu kaca.",
+       "pagedata-title": "Data kaca",
+       "pagedata-not-acceptable": "Teu kapanggih format nu luyu. Jinis MIME nu dirojong: $1",
+       "pagedata-bad-title": "Judul teu sah: $1."
 }
index 3650bec..ce66881 100644 (file)
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (se även [[Special:NewPages|listan över nya sidor]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Visa",
+       "rcfilters-tag-remove": "Ta bort \"$1\"",
        "rcfilters-legend-heading": "<strong>Lista över förkortningar:</strong>",
        "rcfilters-other-review-tools": "<strong>Andra granskningsverktyg</strong>",
        "rcfilters-group-results-by-page": "Gruppera resultat efter sida",
        "rcfilters-hours-title": "Senaste timmarna",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|dag|dagar}}",
        "rcfilters-days-show-hours": "$1 {{PLURAL:$1|timme|timmar}}",
+       "rcfilters-highlighted-filters-list": "Markerade: $1",
        "rcfilters-quickfilters": "Sparade filter",
        "rcfilters-quickfilters-placeholder-title": "Inga länkar har sparats ännu",
        "rcfilters-quickfilters-placeholder-description": "För att spara dina filterinställningar och återanvända dem senare, klicka på bokmärkesikonen under \"Aktiva filter\" nedan.",
index 47214e0..b882534 100644 (file)
@@ -80,7 +80,7 @@
        "category-subcat-count": "{{PLURAL:$2|pintbcyan qaniy ga nanak yaquw qutux uzyuk na pintbcyan quw spgluw niya’.| pintbcyan qaniy ga kwara’ kin na $1 uzyuk na pintbcyan sawn niya uziy, $2 kwara’.}}",
        "category-article-count": "{{PLURAL:$2| pintbcyan qaniy ga ginkwara’ zngazyan tay suruw. | pintbcyan qaniy ga kwara’ kin $1 pintbcyan tay suruw, $2 kwara’.}}",
        "category-file-count": "{{PLURAL:$2|Pintbcyan niya’ qaniy ga nanak yaquw nyux niya’ sbiru’ sa hugal qaniy|Pintbcyan niya’ qaniy ga kwara’ kin na biru’ ka $1, psqunun kwara\nga $2 .}}",
-       "listingcontinuesabbrev": "pin’ubuy sa",
+       "listingcontinuesabbrev": "luhing",
        "about": "Nanu’ quw",
        "newwindow": "(gyahiy na giqas na kktan)",
        "cancel": "laxan",
        "history_small": "Pinhknyan sraral",
        "printableversion": "Kinbalay sa musa’ blaq sp’isat",
        "permalink": "Mrayrhuw na pin’ubuy",
+       "print": "Re’in",
        "view": "Psbaybzih minblaq mita’",
        "view-foreign": "Psbzih mita’ squ$1",
        "edit": "Smr’zyut miru’smr’zyut miru’",
        "nstab-template": "Mopan",
        "nstab-category": "Ps’anak sa mkgluw",
        "mainpage-nstab": "T’ringan na zzngayan",
+       "databaseerror-function": "Pptzyuwaw:$1",
        "missingarticle-diff": "(Kin’ini’ ptnaq:$1, $2)",
+       "internalerror": "pin’qwan tay qsahuy",
        "badtitle": "Ungat zyuwaw na lalu’ na spzyang kkayal",
        "badtitletext": "Sni’ su’ lalu’ na spzyang kkayal su’ qasa ga ungat zyuwaw niya’, ungat ana nanu’ qsahuy niya’, ini’ ga ini’ su’ ’nblayqiy p’ubuy quw pin’ubuy su’ squw bzinah na kay’, ini’ ga ’mubuy sa sni’ naha’ lalu’ sa Wiki’. \nKuna kya wal su’ syun ka biru’ qasa ga kya’a cyux ’magan sa biru’ na ini’ baqiy mita’ hazi’.",
        "viewsource": "Kta quw llpgan aring sa cin’ringan",
        "passwordreset": "T’aring lawziy smi’ mima’",
        "passwordreset-username": "Lalu’ na pptzyuwaw:",
        "passwordreset-email": "Zyusyo na e-meyo’:",
+       "changeemail-none": "(ungat)",
        "resettokens-tokens": "Niwan sawsu’:",
        "bold_sample": "Qthuy na biru’",
        "bold_tip": "Qthuy na biru’",
        "anoneditwarning": "<strong>Smrhuw kmal:</strong> Ini’ su’ kzyup na’. Maha iy wal su’ sbahun ana inu’ ga, nanu’ quw cyux ki’an na  IP su’ ga musa’ ktan kwara’ squliq. Maha iy <strong>[$1 wal mzyup]</strong> ini’ ga <strong>[$2 ps’rux Canghaw]</strong>,sinr’zyut su’ miru’ qaniy ga musa’ niya’ s’agal sa lalu’ su’ ka nyux mmiru’ qaniy smbbaq, ru musa’ magal sa kwara’ ka qqblayqan na hway naha’.",
        "loginreqlink": "mzyup",
        "noarticletext-nopermission": "Ungat ana cikuy knayal sa zyuwaw na qsahuy na lalu’ na kay’ squw nyux ktan sa zngayan qaniy.\nAna su’ s’usa’ sa zngayan tay bzinah \n[[Special:Search/{{PAGENAME}}|hmkangi’ sa puqing lalu’ na zngayan qaniy]], ini’ ga <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hmkangi’ sa m’ubuy sa zyuwaw na bbrwan qutux qutux ryax]</span>,ga ungat kinri’ su’ ms’rux sa zngayan qaniy.",
+       "updated": "(Sinbahan)",
        "note": "<strong> pinkita’ niya’ sa pinzga’ :</strong>",
        "editing": "Nyuw smr’zyut miru’ $1",
        "editingsection": "smr’zyut miru’ $1 (gnaygay binrwan)",
        "moveddeleted-notice": "Wal pzyutan quw zngayan qaniy.\nNyux sbiru’ hugal quw wal pzyutan na zngazyan squw sinhzyuwan bniru’ sa qutux qutux ryax aki’ baqun sa ggalan pspngan mita’.",
        "content-model-wikitext": "Wikitext",
        "content-model-text": "biru’ mlmlux",
+       "content-json-empty-object": "Ungat ana nanu’ quw nnanu’ niya’",
+       "content-json-empty-array": "Ungat ana nanu’ sni’ binrwan naha’",
        "viewpagelogs": "Inblaq mita’ quw bniru’ sa zngazyan qaniy",
        "currentrev-asof": "Spzyang balay giqas sinbahan squw  $1",
        "revisionasof": "Gaga’ na pptzyuwaw sa qutux qutux binkgan gaga’",
        "nextrevision": "Ssbahun babaw nya’→",
        "currentrevisionlink": "Pzyang giqas na sinhzi’",
        "cur": "misuw qaniy ga",
+       "next": "tay suruw",
        "last": "tay glaing",
+       "page_first": "t’ringan balay na llpgan",
+       "page_last": "pucing balay llpgan",
        "histlast": "giqas balay",
        "historysize": "({{PLURAL:$1|$1 bzyaqan kkayal na llpgan}})",
        "rev-delundel": "Smbah sa musa’ slwan mita’",
        "search-showingresults": "{{PLURAL:$4|tay <strong>$1</strong>pucing niya’, psqunun lga <strong>$3</strong> |tay <strong>$1 - $2</strong> pucing nya’, psqunun lga <strong>$3</strong> pucing nya’}}",
        "search-nonefound": "Ktan qu bniru’ niya’ ga ungat quw musa’ mtnaq sa tthuzyay sa awsa’ hmkangi’.",
        "powersearch-toggleall": "Kwara’",
+       "powersearch-togglenone": "Ungat",
        "mypreferences": "Sni’ maku’",
        "prefs-skin": "Ktan na rawziq",
        "skin-preview": "Pnaynama’ mita’",
        "saveprefs": "Cucun",
        "prefs-editing": "Smr’zyut miru’",
        "searchresultshead": "Hhkangi’",
+       "timezonelegend": "Zikang na ini’ ptanaq rhzyal",
        "timezoneregion-africa": "Xweco",
        "timezoneregion-america": "Meco",
        "timezoneregion-antarctica": "Nancicow",
        "prefs-namespaces": "Kungcyen na sslalu’",
        "default": "pinnama’ smi’",
        "prefs-files": "biru’ na zayzyuwaw",
+       "prefs-custom-css": "Sinpung nanak CSS",
        "youremail": "e-meyo’:",
        "username": "{{GENDER:$1|Lalu’ na pptzyuwaw}}:",
        "yourlanguage": "Kay’:",
        "email": "e-meyo’",
        "prefs-i18n": "Koksayka",
+       "prefs-dateformat": "Ksu’ na ryax",
        "prefs-timeoffset": "Kinini’ ptnaq na zikang",
        "prefs-advancedediting": "Kwara’ hya’ g a syensyang",
        "prefs-preview": "Pnaynama’ mita’",
        "recentchanges-submit": "Pkaykita’",
        "rcfilters-filterlist-title": "Hhkangi’",
        "rcfilters-filter-user-experience-level-registered-label": "Pinbiru’",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Iyat pinbiru’ na’",
        "rcfilters-filter-user-experience-level-learner-label": "Misan mqbaq",
        "rcfilters-filter-bots-label": "squliq na kikay",
+       "rcfilters-filter-unpatrolled-label": "Iyat gnawzyagan mita’ na’",
        "rcfilters-filtergroup-significance": "Spzyang krhun na zyuwaw",
+       "rcfilters-filter-categorization-label": "Pintbcyan na sinbahan",
        "rclistfrom": "Inkahul misan sinbahan sa $2, $3",
        "rcshowhideminor": "$1 mszik sa ssr’tun",
        "rcshowhideminor-show": "Pkaykita’",
        "filedesc": "Lalu’ na spzyang bbiru’ su’",
        "fileuploadsummary": "Lalu’ na spzyang bbiru’ su’:",
        "filesource": "Tkhulan:",
+       "upload-file-error": "pin’qwan tay qsahuy",
        "upload-dialog-button-back": "’bzinah",
+       "upload-dialog-button-done": "Tmasuq",
        "upload-dialog-button-save": "Cucun",
        "upload-form-label-infoform-description": "Miblaq pnqzyu’ sa zyuwaw",
+       "upload-form-label-usage-title": "Pptzyuwaw",
        "upload-form-label-infoform-date": "Ryax",
        "license": "Biru’ na gaga’ nqu snwayal hmriq sa kinri’:",
        "license-header": "Biru’ na gaga’ nqu snwayal hmriq sa kinri’",
        "filerevert-comment": "’ringan:",
        "filedelete-comment": "’ringan:",
        "filedelete-submit": "’muyut",
+       "download": "syacay",
        "randompage": "Random page",
        "randomincategory-submit": "Musa’",
        "statistics": "Sinqunan plpuw",
        "pageswithprop-submit": "Musa’",
        "brokenredirects-edit": "Smr’zyut miru’",
        "brokenredirects-delete": "’muyut",
+       "withoutinterwiki-legend": "T’ringan na biru’",
        "withoutinterwiki-submit": "Pkaykita’",
        "nbytes": "$1 {{PLURAL:$1|qutux llpgan na bzyaqan kkayal}}",
        "nmembers": "$1 {{PLURAL:$1|pinglgan}}",
        "prefixindex-submit": "Pkaykita’",
+       "protectedpages-timestamp": "Ryax zikang",
        "protectedpages-page": "Zzngayan",
        "protectedpages-expiry": "Sinpngan ryax",
        "protectedpages-reason": "’ringan",
        "newpages-submit": "Pkaykita’",
        "newpages-username": "Lalu’ na pptzyuwaw:",
        "move": "Smhzi’",
+       "notargettitle": "Ungat sinngusan niya’",
        "pager-older-n": "{{PLURAL:$1|smural hazi’ quw $1}}",
        "suppress": "Shtuy",
+       "apisandbox-retry": "Talam lawziy",
        "apisandbox-results": "Pcingan balay",
        "apisandbox-continue": "Siy lhingiy",
        "booksources": "Brbiru’ na inkhulan na pila’",
        "log": "Pinhknyan sraral",
        "logeventslist-submit": "Pkaykita’",
        "checkbox-all": "Kwara’",
+       "checkbox-none": "Ungat",
+       "checkbox-invert": "Pssyuk mwazyaw",
        "allpages": "Kwara’ biru’ na zngyan",
        "nextpage": "Llpgan tay suruw ($1)",
        "prevpage": "Llpgan tay glaing ($1)",
        "listgrants-rights": "Pcingan na sinpngan",
        "emailusername": "Lalu’ na pptzyuwaw:",
        "emailusernamesubmit": "Pawsun",
+       "emailto": "Sginbang a:",
        "emailsubject": "Spzyang na ptzyuwaw:",
        "emailmessage": "Pintkaykay’:",
        "watchlist": "Cyes’ cingtan",
        "restriction-move": "Smhzi’",
        "restriction-create": "Ps’rux",
        "undeletebtn": "Psbzih",
+       "undeleteinvert": "Pssyuk mwazyaw",
        "undeletecomment": "’ringan:",
        "undelete-search-submit": "Hhkangi’",
        "undelete-show-file-submit": "Aw’",
        "whatlinkshere-submit": "Musa’",
        "ipbreason": "’ringan:",
        "autoblocklist-submit": "Hhkangi’",
+       "blocklist-timestamp": "Ryax zikang",
+       "blocklist-target": "Sinpngan na sinngusan",
        "blocklist-expiry": "Sinpngan ryax",
        "blocklist-reason": "’ringan",
        "ipblocklist-submit": "Hhkangi’",
        "export": "Pawsa’ sa bzinah quwzngayan",
        "export-submit": "Pawsa’",
        "allmessages-filter-legend": "Hhkangi’",
+       "allmessages-filter-unmodified": "Iyat sinbah",
        "allmessages-filter-all": "Kwara’ biru’ na zngyan",
        "allmessages-language": "Kay’:",
        "allmessages-filter-submit": "Mtzyuwaw",
        "simpleantispam-label": "Kmyagal sa kana ptayqihiy na niwan saysyup na kay’ na nnanu.\n<strong>Laxiy</strong> brwaniy sa bbrwan qaniy!",
        "pageinfo-language-change": "smbah",
        "pageinfo-content-model-change": "smbah",
+       "pageinfo-robot-index": "Snwalan",
        "pageinfo-watchers": "Number of page watchers",
        "pageinfo-toolboxlink": "Zzngayan na ggalan qqbaqan",
        "pageinfo-contentpage-yes": "Aw’",
        "file-info-gif-looped": "syunxwan",
        "file-info-png-looped": "syunxwan",
        "newimages-legend": "Hhkangi’",
+       "gallery-slideshow-toggle": "Sbah mita' sa syasing bzinah",
        "ilsubmit": "Hhkangi’",
+       "bydate": "skahul sa ryax",
        "metadata": "Cyuens’cryaw",
        "metadata-help": "Taki’ sa biru’ na kwara’ zayzyuwaw qaniy ga kwara’ kin na zayzyuwaw bbzinah, kwara’ quw zyuwaw sqaniy ga hazi’ na kahul sa pins’rux na Suwe syangciy ini’ ga tmrang squw cyux ps’rux squw Sken ini’ ga cyux tmrang smr’zyut sa Suwe . \nMaha iy wal sbahun squw puqing balay na biru’ cyux syan squw ana nanu’ zyuwaw lga, iyat hazi’ baqun minblaq ppwah kruma’ quw wal minblayqun miru’ na wayal sbahun na zyuwaw hya’ la.",
        "metadata-fields": "maki’ squw bnaykgan na pintkaykay’ ka qsahuy na Cyens’cryaw EXIF ga, syun niya’ sa zzngazyan kwara’ kin na Syasing, nanu’ iy mhiriq quw binrwan niya’ Cyens’cryaw lga nanak yaquw nyux sbiru’ hugal ka pintkaykay’ qaniy msthay.\ncyuens’ cryaw bzinah hya’ lga skun niya’ sa llqingun qasa hya’. \n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "exif-orientation": "Ppiray sa iyat ptkkiy ktan quw syasing",
        "exif-xresolution": "Sweping Cyesitu’",
        "exif-yresolution": "Cwec’ Cyesitu’",
+       "exif-primarychromaticities": "Spzyang balay na iro’",
        "exif-datetime": "Ryax na sinbah Tang’an squw zikang na sinbahan Tang’an",
        "exif-make": "Squliq ka pkbalay squw Syasinki’",
        "exif-model": "Lalu’ na kinblayan Syasingki’",
        "exif-colorspace": "Hhalan mwazyaw sa qqlah sa ini’ ptnaq  kktan na bbiru’",
        "exif-datetimeoriginal": "Ryax na sni’ sa Craw squw zikang na sni’ sa Craw",
        "exif-datetimedigitized": "Ryax na minsuwexwa’ squw zikang na minsuwexwa’",
+       "exif-fnumber": "Kinsmyaxan na syasingki’",
+       "exif-subjectdistance": "Kintwahiq zyuwaw na sinngusan ppsingan",
+       "exif-lightsource": "Puqing na syax",
        "exif-flash": "Pnilaw na pyax pyax",
+       "exif-subjectarea": "Rhzyal cyux ki'an na spzyang kkayal",
+       "exif-subjectlocation": "Cyux ki'an na spzyang kkayal",
+       "exif-whitebalance": "Wayt belns",
        "exif-sharpness": "Srp nes",
        "exif-keywords": "Biru’ na Galan sa hhkangi qsahuy na tenaw",
        "exif-headline": "Lalu’",
        "exif-iimcategory": "Ps’anak sa mkgluw",
        "exif-identifier": "Biru’ na sinbbaq sa cyux naha’ ptcyuwagun",
        "exif-label": "Qinlah lalu’",
+       "exif-compression-1": "Ini’ aniy pkcikuy na’",
+       "exif-unknowndate": "Ini’ bqbaqiy ginlaylwan na ryax",
        "exif-orientation-1": "Mutuw galan sa puqing pspngan kwara’ na squliq",
+       "exif-orientation-2": "Ptbangun pssyuk",
+       "exif-exposureprogram-0": "Ini’ syaniy kay’ na’",
+       "exif-exposureprogram-1": "Szyuwi’ na qba’",
        "exif-meteringmode-0": "Ini’ bqbaqiy ginlaylwan",
+       "exif-meteringmode-1": "Pinqsugan",
+       "exif-meteringmode-4": "Multi-Spot",
        "exif-meteringmode-255": "Bzinah",
        "exif-lightsource-0": "Ini’ bqbaqiy ginlaylwan",
+       "exif-lightsource-1": "Snyaxan wagi’",
        "exif-lightsource-2": "Pnilaw na inkwang",
        "exif-lightsource-4": "Pnilaw na pyax pyax",
        "exif-lightsource-10": "Mzyulung",
+       "exif-lightsource-11": "Tay minkum",
        "exif-focalplaneresolutionunit-2": "incun",
+       "exif-sensingmethod-1": "Ini’ syaniy kay’ na’",
        "exif-customrendered-0": "Kwara’ hya’ ga  cngsyu’",
+       "exif-exposuremode-1": "Pinspsyax na squliq",
        "exif-scenecapturetype-0": "Galan pspngan",
+       "exif-scenecapturetype-2": "Syasing na squliq",
+       "exif-scenecapturetype-3": "Knita’ sa mlhngan na qlqalang",
+       "exif-gaincontrol-0": "Ungat",
+       "exif-gaincontrol-1": "Kcikuy cikay tmwang",
+       "exif-gaincontrol-2": "Kpzyux cikay tmwang",
+       "exif-gaincontrol-3": "Kcikuy cikay hmhuwi’",
+       "exif-gaincontrol-4": "Kpzyux cikay hmhuwi’",
        "exif-contrast-0": "Galan pspngan",
+       "exif-contrast-2": "lawkah/mhitu’",
        "exif-saturation-0": "Galan pspngan",
        "exif-sharpness-0": "Galan pspngan",
+       "exif-sharpness-2": "lawkah/mhitu’",
        "exif-subjectdistancerange-0": "Ini’ bqbaqiy ginlaylwan",
+       "exif-subjectdistancerange-1": "Mekro",
        "exif-gpslatitude-n": "Pewe’",
        "exif-gpslatitude-s": "Nanwe’",
+       "exif-gpslongitude-e": "Ist longzicyut",
        "exif-gpslongitude-w": "Sicing",
        "exif-gpsdestdistance-k": "Kirometa’",
        "exif-gpsdestdistance-m": "Inri’",
        "exif-dc-rights": "Pcingan na sinpngan",
        "exif-iimcategory-hth": "Blaq hi’",
        "exif-iimcategory-spo": "Tayyok",
+       "exif-iimcategory-wea": "Kayal",
        "exif-urgency-normal": "Galan pspngan ($1)",
        "namespacesall": "kwara’",
        "monthsall": "kwara’",
        "imgmultipagenext": "llpgan tay suruw →",
        "imgmultigo": "Aw’!",
        "img-lang-go": "Musa’",
+       "ascending_abbrev": "pskkrawn paybkuw",
        "table_pager_next": "Llpgan tay suruw",
        "table_pager_prev": "Llpgan tay glaing",
+       "table_pager_first": "T’ringan balay na llpgan",
+       "table_pager_last": "Pucing balay llpgan",
        "table_pager_limit_submit": "Pawsun",
        "watchlistedit-raw-titles": "Lalu’:",
        "watchlistedit-clear-titles": "Lalu’:",
        "redirect-lookup": "Hmkangi’:",
        "redirect-user": "Mning ID",
        "redirect-file": "Lalu’ na tang’an",
+       "redirect-not-exists": "Ini' ’luwiy biru’ na llpgan",
        "fileduplicatesearch-filename": "Lalu’ na tang’an:",
        "fileduplicatesearch-submit": "Hhkangi’",
        "specialpages": "Mnanak na zzngayan",
        "tags-delete-reason": "’ringan:",
        "tags-activate-reason": "’ringan:",
        "tags-deactivate-reason": "’ringan:",
+       "tags-edit-existing-tags-none": "<em>Ungat<em>",
        "tags-edit-reason": "’ringan:",
+       "comparepages": "Pspung sa zzngazyan",
        "compare-page2": "Zzngayan 2",
+       "compare-submit": "Pspngun",
        "diff-form": "Kin’ini’ ptnaq",
        "permanentlink": "Mrayrhuw na pin’ubuy",
        "htmlform-submit": "Pawsun",
        "logentry-move-move": "$1 {{GENDER:$2|wal shzyun}}zngyan $3 squw $4",
        "logentry-newusers-create": "Wayal tmasuq {{GENDER:$2|ps’rux sq }} quw cin canghaw $1",
        "logentry-upload-upload": "$1 {{GENDER:$2|wal pawsun }} $3",
+       "rightsnone": "(ungat)",
        "feedback-back": "’bzinah",
+       "feedback-close": "Tmasuq",
        "feedback-message": "Pintkaykay’:",
        "feedback-subject": "Spzyang na ptzyuwaw:",
        "feedback-submit": "Pawsun",
        "mediastatistics-header-text": "Biru’ mlmlux",
        "special-characters-group-latin": "Biru’ na lating",
        "special-characters-group-symbols": "Sni’ naha’ sa gaga’ na bbiru’",
+       "special-characters-group-greek": "Biru’ na Grisya’",
        "special-characters-group-persian": "Przn",
        "special-characters-group-bangla": "Biru’ na Bengr",
        "special-characters-group-thai": "Biru’ na Taykok",
+       "special-characters-group-khmer": "Biru' na Khmer",
        "mw-widgets-titleinput-description-new-page": "ungat zzngayan",
+       "mw-widgets-categoryselector-add-category-placeholder": "Twangiy sa pintbcyan...",
        "log-action-filter-all": "Kwara’ biru’ na zngyan",
        "log-action-filter-block-block": "Qmhut",
+       "log-action-filter-delete-event": "Pzyutan binrwan",
+       "log-action-filter-patrol-patrol": "hkangyun na qba’",
+       "log-action-filter-rights-rights": "Sbah na qba’",
        "authmanager-email-label": "e-meyo’",
        "authmanager-email-help": "Zyusyo na e-meyo’"
 }
index d844fb8..0986d7e 100644 (file)
        "recentchanges-legend-heading": "<strong>اختصارات:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (نیز [[Special:NewPages|جدید صفحات کی فہرست]]) ملاحظہ فرمائیں",
        "recentchanges-submit": "دکھائیں",
+       "rcfilters-filterlist-feedbacklink": "ہمیں بتایئے کہ آپ آپ ان (نئے) چھننی والے آلات کے بارے میں کیا سوچتے ہیں",
        "rcfilters-filter-user-experience-level-experienced-label": "تجربہ کار صارف",
+       "rcfilters-watchlist-editWatchlist-button": "اپنی زیرنظر صفحات کی فہرست ترمیم کیجیے",
+       "rcfilters-watchlist-showupdated": "تبدیلیوں کے رونما ہونے کے بعد جن صفحات کا آپ دورہ نہیں کیے ہیں وہ <strong>جلی</strong> میں، ٹھوس مارکروں کے ساتھ ہیں۔",
        "rcnotefrom": "ذیل میں <strong>$2</strong> سے کی گئی {{PLURAL:$5|تبدیلی|تبدیلیاں}} <strong>$1</strong> تک دکھائی جا رہی ہیں۔",
        "rclistfrom": "$2، $3ء سے ہونے والی نئی تبدیلیاں دکھائیں",
        "rcshowhideminor": "معمولی ترامیم $1",
index 9ab3d48..fe2be9b 100644 (file)
        "compare-invalid-title": "Tên trang chỉ định không hợp lệ.",
        "compare-title-not-exists": "Tên trang chỉ định không tồn tại.",
        "compare-revision-not-exists": "Phiên bản chỉ định không tồn tại.",
-       "diff-form": "'''biểu mẫu'''",
+       "diff-form": "Khác biệt",
        "dberr-problems": "Xin lỗi! Trang này đang gặp phải những khó khăn về kỹ thuật.",
        "dberr-again": "Xin thử đợi vài phút rồi tải lại trang.",
        "dberr-info": "(Không thể liên lạc với máy chủ cơ sở dữ liệu: $1)",
index eed8eec..09810ba 100644 (file)
        "rcfilters-empty-filter": "没有激活的过滤器。已显示所有贡献。",
        "rcfilters-filterlist-title": "过滤器",
        "rcfilters-filterlist-whatsthis": "这些是怎样工作的?",
-       "rcfilters-filterlist-feedbacklink": "å\9c¨æ\96°ï¼\88æµ\8bè¯\95ç\89\88ï¼\89è¿\87滤å\99¨ä¸­æ\8f\90ä¾\9bå\8f\8dé¦\88",
+       "rcfilters-filterlist-feedbacklink": "å\91\8aè¯\89æ\88\91们æ\82¨å¯¹è¿\99äº\9bï¼\88æ\96°ï¼\89è¿\87滤工å\85·æ\9c\89ä»\80ä¹\88æ\83³æ³\95",
        "rcfilters-highlightbutton-title": "高亮结果",
        "rcfilters-highlightmenu-title": "选择颜色",
        "rcfilters-highlightmenu-help": "选择颜色来高亮该属性",
        "rcfilters-liveupdates-button-title-on": "关闭实时更新",
        "rcfilters-liveupdates-button-title-off": "显示新更改(如有)",
        "rcfilters-watchlist-markSeen-button": "标记所有更改为已查看",
+       "rcfilters-watchlist-editWatchlist-button": "编辑您的监视页面的列表",
+       "rcfilters-watchlist-showupdated": "自更改发生以来,对您尚未访问的页面做出的更改以<strong>粗体</strong>显示,并带有立体标记。",
        "rcnotefrom": "下面{{PLURAL:$5|是}}<strong>$3 $4</strong>之后的更改(最多显示<strong>$1</strong>个)。",
        "rclistfromreset": "重置时间选择",
        "rclistfrom": "显示$3 $2之后的新更改",
diff --git a/maintenance/archives/patch-categorylinks-fix-pk.sql b/maintenance/archives/patch-categorylinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..20bc716
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/categorylinks DROP KEY /*i*/cl_from, ADD PRIMARY KEY (cl_from,cl_to);
diff --git a/maintenance/archives/patch-comment-table.sql b/maintenance/archives/patch-comment-table.sql
new file mode 100644 (file)
index 0000000..c8bf958
--- /dev/null
@@ -0,0 +1,59 @@
+--
+-- patch-comment-table.sql
+--
+-- T166732. Add a `comment` table and various columns (and temporary tables) to reference it.
+
+CREATE TABLE /*_*/comment (
+  comment_id bigint unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  comment_hash INT NOT NULL,
+  comment_text BLOB NOT NULL,
+  comment_data BLOB
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/comment_hash ON /*_*/comment (comment_hash);
+
+CREATE TABLE /*_*/revision_comment_temp (
+  revcomment_rev int unsigned NOT NULL,
+  revcomment_comment_id bigint unsigned NOT NULL,
+  PRIMARY KEY (revcomment_rev, revcomment_comment_id)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/revcomment_rev ON /*_*/revision_comment_temp (revcomment_rev);
+
+CREATE TABLE /*_*/image_comment_temp (
+  imgcomment_name varchar(255) binary NOT NULL,
+  imgcomment_description_id bigint unsigned NOT NULL,
+  PRIMARY KEY (imgcomment_name, imgcomment_description_id)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/imgcomment_name ON /*_*/image_comment_temp (imgcomment_name);
+
+ALTER TABLE /*_*/revision
+  ALTER COLUMN rev_comment SET DEFAULT '';
+
+ALTER TABLE /*_*/archive
+  ALTER COLUMN ar_comment SET DEFAULT '',
+  ADD COLUMN ar_comment_id bigint unsigned NOT NULL DEFAULT 0 AFTER ar_comment;
+
+ALTER TABLE /*_*/ipblocks
+  ALTER COLUMN ipb_reason SET DEFAULT '',
+  ADD COLUMN ipb_reason_id bigint unsigned NOT NULL DEFAULT 0 AFTER ipb_reason;
+
+ALTER TABLE /*_*/image
+  ALTER COLUMN img_description SET DEFAULT '';
+
+ALTER TABLE /*_*/oldimage
+  ALTER COLUMN oi_description SET DEFAULT '',
+  ADD COLUMN oi_description_id bigint unsigned NOT NULL DEFAULT 0 AFTER oi_description;
+
+ALTER TABLE /*_*/filearchive
+  ADD COLUMN fa_deleted_reason_id bigint unsigned NOT NULL DEFAULT 0 AFTER fa_deleted_reason,
+  ALTER COLUMN fa_description SET DEFAULT '',
+  ADD COLUMN fa_description_id bigint unsigned NOT NULL DEFAULT 0 AFTER fa_description;
+
+ALTER TABLE /*_*/recentchanges
+  ADD COLUMN rc_comment_id bigint unsigned NOT NULL DEFAULT 0 AFTER rc_comment;
+
+ALTER TABLE /*_*/logging
+  ADD COLUMN log_comment_id bigint unsigned NOT NULL DEFAULT 0 AFTER log_comment;
+
+ALTER TABLE /*_*/protected_titles
+  ALTER COLUMN pt_reason SET DEFAULT '',
+  ADD COLUMN pt_reason_id bigint unsigned NOT NULL DEFAULT 0 AFTER pt_reason;
diff --git a/maintenance/archives/patch-imagelinks-fix-pk.sql b/maintenance/archives/patch-imagelinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..e66500f
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/imagelinks DROP KEY /*i*/il_from, ADD PRIMARY KEY (il_from,il_to);
diff --git a/maintenance/archives/patch-iwlinks-fix-pk.sql b/maintenance/archives/patch-iwlinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..1dd5220
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/iwlinks DROP KEY /*i*/iwl_from, ADD PRIMARY KEY (iwl_from,iwl_prefix,iwl_title);
diff --git a/maintenance/archives/patch-langlinks-fix-pk.sql b/maintenance/archives/patch-langlinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..e3ac312
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/langlinks DROP KEY /*i*/ll_from, ADD PRIMARY KEY (ll_from,ll_lang);
diff --git a/maintenance/archives/patch-log_search-fix-pk.sql b/maintenance/archives/patch-log_search-fix-pk.sql
new file mode 100644 (file)
index 0000000..51bfdf5
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/log_search DROP KEY /*i*/ls_field_val, ADD PRIMARY KEY (ls_field,ls_value,ls_log_id);
diff --git a/maintenance/archives/patch-log_search-rename-index.sql b/maintenance/archives/patch-log_search-rename-index.sql
deleted file mode 100644 (file)
index 7e1113e..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
--- Rename the primary unique index from PRIMARY to ls_field_val
--- This is for MySQL only and is necessary only for databases which were updated
--- between MW 1.16 development revisions r50567 and r51465.
-ALTER TABLE /*_*/log_search
-       DROP PRIMARY KEY,
-       ADD UNIQUE INDEX ls_field_val (ls_field,ls_value,ls_log_id);
-
diff --git a/maintenance/archives/patch-module_deps-fix-pk.sql b/maintenance/archives/patch-module_deps-fix-pk.sql
new file mode 100644 (file)
index 0000000..2338df0
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/module_deps DROP KEY /*i*/md_module_skin, ADD PRIMARY KEY (md_module,md_skin);
diff --git a/maintenance/archives/patch-objectcache-fix-pk.sql b/maintenance/archives/patch-objectcache-fix-pk.sql
new file mode 100644 (file)
index 0000000..cd55716
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/objectcache DROP KEY /*i*/keyname, ADD PRIMARY KEY (keyname);
diff --git a/maintenance/archives/patch-pagelinks-fix-pk.sql b/maintenance/archives/patch-pagelinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..e269143
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/pagelinks DROP INDEX /*i*/pl_from, ADD PRIMARY KEY (pl_from,pl_namespace,pl_title);
diff --git a/maintenance/archives/patch-querycache_info-fix-pk.sql b/maintenance/archives/patch-querycache_info-fix-pk.sql
new file mode 100644 (file)
index 0000000..94f3c1d
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/querycache_info DROP KEY /*i*/qci_type, ADD PRIMARY KEY (qci_type);
diff --git a/maintenance/archives/patch-site_stats-fix-pk.sql b/maintenance/archives/patch-site_stats-fix-pk.sql
new file mode 100644 (file)
index 0000000..d32adf3
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/site_stats DROP KEY /*i*/ss_row_id, ADD PRIMARY KEY (ss_row_id);
diff --git a/maintenance/archives/patch-templatelinks-fix-pk.sql b/maintenance/archives/patch-templatelinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..8aca510
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/templatelinks DROP INDEX /*i*/tl_from, ADD PRIMARY KEY (tl_from,tl_namespace,tl_title);
diff --git a/maintenance/archives/patch-text-fix-pk.sql b/maintenance/archives/patch-text-fix-pk.sql
new file mode 100644 (file)
index 0000000..b546333
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/text DROP KEY /*i*/old_id, ADD PRIMARY KEY (old_id);
diff --git a/maintenance/archives/patch-transcache-fix-pk.sql b/maintenance/archives/patch-transcache-fix-pk.sql
new file mode 100644 (file)
index 0000000..2e8fea1
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/transcache DROP KEY /*i*/tc_url_idx, ADD PRIMARY KEY (tc_url);
diff --git a/maintenance/archives/patch-user_former_groups-fix-pk.sql b/maintenance/archives/patch-user_former_groups-fix-pk.sql
new file mode 100644 (file)
index 0000000..9a776ca
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/user_former_groups DROP KEY /*i*/ufg_user_group, ADD PRIMARY KEY (ufg_user,ufg_group);
diff --git a/maintenance/archives/patch-user_properties-fix-pk.sql b/maintenance/archives/patch-user_properties-fix-pk.sql
new file mode 100644 (file)
index 0000000..5d51b78
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE /*_*/user_properties DROP KEY /*i*/user_properties_user_property, ADD PRIMARY KEY (up_user,up_property);
diff --git a/maintenance/migrateComments.php b/maintenance/migrateComments.php
new file mode 100644 (file)
index 0000000..4313806
--- /dev/null
@@ -0,0 +1,291 @@
+<?php
+/**
+ * Migrate comments from pre-1.30 columns to the 'comment' table
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Maintenance script that migrates comments from pre-1.30 columns to the
+ * 'comment' table
+ *
+ * @ingroup Maintenance
+ */
+class MigrateComments extends LoggedUpdateMaintenance {
+       public function __construct() {
+               parent::__construct();
+               $this->addDescription( 'Migrates comments from pre-1.30 columns to the \'comment\' table' );
+               $this->setBatchSize( 100 );
+       }
+
+       protected function getUpdateKey() {
+               return __CLASS__;
+       }
+
+       protected function updateSkippedMessage() {
+               return 'comments already migrated.';
+       }
+
+       protected function doDBUpdates() {
+               global $wgCommentTableSchemaMigrationStage;
+
+               if ( $wgCommentTableSchemaMigrationStage < MIGRATION_WRITE_NEW ) {
+                       $this->output(
+                               "...cannot update while \$wgCommentTableSchemaMigrationStage < MIGRATION_WRITE_NEW\n"
+                       );
+                       return false;
+               }
+
+               $this->migrateToTemp(
+                       'revision', 'rev_id', 'rev_comment', 'revcomment_rev', 'revcomment_comment_id'
+               );
+               $this->migrate( 'archive', 'ar_id', 'ar_comment' );
+               $this->migrate( 'ipblocks', 'ipb_id', 'ipb_reason' );
+               $this->migrateToTemp(
+                       'image', 'img_name', 'img_description', 'imgcomment_name', 'imgcomment_description_id'
+               );
+               $this->migrate( 'oldimage', [ 'oi_name', 'oi_timestamp' ], 'oi_description' );
+               $this->migrate( 'filearchive', 'fa_id', 'fa_deleted_reason' );
+               $this->migrate( 'filearchive', 'fa_id', 'fa_description' );
+               $this->migrate( 'recentchanges', 'rc_id', 'rc_comment' );
+               $this->migrate( 'logging', 'log_id', 'log_comment' );
+               $this->migrate( 'protected_titles', [ 'pt_namespace', 'pt_title' ], 'pt_reason' );
+               return true;
+       }
+
+       /**
+        * Fetch comment IDs for a set of comments
+        * @param IDatabase $dbw
+        * @param array &$comments Keys are comment names, values will be set to IDs.
+        * @return int Count of added comments
+        */
+       private function loadCommentIDs( IDatabase $dbw, array &$comments ) {
+               $count = 0;
+               $needComments = $comments;
+
+               while ( true ) {
+                       $where = [];
+                       foreach ( $needComments as $need => $dummy ) {
+                               $where[] = $dbw->makeList(
+                                       [
+                                               'comment_hash' => CommentStore::hash( $need, null ),
+                                               'comment_text' => $need,
+                                       ],
+                                       LIST_AND
+                               );
+                       }
+
+                       $res = $dbw->select(
+                               'comment',
+                               [ 'comment_id', 'comment_text' ],
+                               [
+                                       $dbw->makeList( $where, LIST_OR ),
+                                       'comment_data' => null,
+                               ],
+                               __METHOD__
+                       );
+                       foreach ( $res as $row ) {
+                               $comments[$row->comment_text] = $row->comment_id;
+                               unset( $needComments[$row->comment_text] );
+                       }
+
+                       if ( !$needComments ) {
+                               break;
+                       }
+
+                       $dbw->insert(
+                               'comment',
+                               array_map( function ( $v ) {
+                                       return [
+                                               'comment_hash' => CommentStore::hash( $v, null ),
+                                               'comment_text' => $v,
+                                       ];
+                               }, array_keys( $needComments ) ),
+                               __METHOD__
+                       );
+                       $count += $dbw->affectedRows();
+               }
+               return $count;
+       }
+
+       /**
+        * Migrate comments in a table.
+        *
+        * Assumes any row with the ID field non-zero have already been migrated.
+        * Assumes the new field name is the same as the old with '_id' appended.
+        * Blanks the old fields while migrating.
+        *
+        * @param string $table Table to migrate
+        * @param string|string[] $primaryKey Primary key of the table.
+        * @param string $oldField Old comment field name
+        */
+       protected function migrate( $table, $primaryKey, $oldField ) {
+               $newField = $oldField . '_id';
+               $primaryKey = (array)$primaryKey;
+               $pkFilter = array_flip( $primaryKey );
+               $this->output( "Beginning migration of $table.$oldField to $table.$newField\n" );
+
+               $dbw = $this->getDB( DB_MASTER );
+               $next = '1=1';
+               $countUpdated = 0;
+               $countComments = 0;
+               while ( true ) {
+                       // Fetch the rows needing update
+                       $res = $dbw->select(
+                               $table,
+                               array_merge( $primaryKey, [ $oldField ] ),
+                               [
+                                       $newField => 0,
+                                       $next,
+                               ],
+                               __METHOD__,
+                               [
+                                       'ORDER BY' => $primaryKey,
+                                       'LIMIT' => $this->mBatchSize,
+                               ]
+                       );
+                       if ( !$res->numRows() ) {
+                               break;
+                       }
+
+                       // Collect the distinct comments from those rows
+                       $comments = [];
+                       foreach ( $res as $row ) {
+                               $comments[$row->$oldField] = 0;
+                       }
+                       $countComments += $this->loadCommentIDs( $dbw, $comments );
+
+                       // Update the existing rows
+                       foreach ( $res as $row ) {
+                               $dbw->update(
+                                       $table,
+                                       [
+                                               $newField => $comments[$row->$oldField],
+                                               $oldField => '',
+                                       ],
+                                       array_intersect_key( (array)$row, $pkFilter ) + [
+                                               $newField => 0
+                                       ],
+                                       __METHOD__
+                               );
+                               $countUpdated += $dbw->affectedRows();
+                       }
+
+                       // Calculate the "next" condition
+                       $next = '';
+                       $prompt = [];
+                       for ( $i = count( $primaryKey ) - 1; $i >= 0; $i-- ) {
+                               $field = $primaryKey[$i];
+                               $prompt[] = $row->$field;
+                               $value = $dbw->addQuotes( $row->$field );
+                               if ( $next === '' ) {
+                                       $next = "$field > $value";
+                               } else {
+                                       $next = "$field > $value OR $field = $value AND ($next)";
+                               }
+                       }
+                       $prompt = join( ' ', array_reverse( $prompt ) );
+                       $this->output( "... $prompt\n" );
+               }
+
+               $this->output(
+                       "Completed migration, updated $countUpdated row(s) with $countComments new comment(s)\n"
+               );
+       }
+
+       /**
+        * Migrate comments in a table to a temporary table.
+        *
+        * Assumes any row with the ID field non-zero have already been migrated.
+        * Assumes the new table is named "{$table}_comment_temp", and it has two
+        * columns, in order, being the primary key of the original table and the
+        * comment ID field.
+        * Blanks the old fields while migrating.
+        *
+        * @param string $oldTable Table to migrate
+        * @param string $primaryKey Primary key of the table.
+        * @param string $oldField Old comment field name
+        * @param string $newPrimaryKey Primary key of the new table.
+        * @param string $newField New comment field name
+        */
+       protected function migrateToTemp( $table, $primaryKey, $oldField, $newPrimaryKey, $newField ) {
+               $newTable = $table . '_comment_temp';
+               $this->output( "Beginning migration of $table.$oldField to $newTable.$newField\n" );
+
+               $dbw = $this->getDB( DB_MASTER );
+               $next = [];
+               $countUpdated = 0;
+               $countComments = 0;
+               while ( true ) {
+                       // Fetch the rows needing update
+                       $res = $dbw->select(
+                               [ $table, $newTable ],
+                               [ $primaryKey, $oldField ],
+                               [ $newPrimaryKey => null ] + $next,
+                               __METHOD__,
+                               [
+                                       'ORDER BY' => $primaryKey,
+                                       'LIMIT' => $this->mBatchSize,
+                               ],
+                               [ $newTable => [ 'LEFT JOIN', "{$primaryKey}={$newPrimaryKey}" ] ]
+                       );
+                       if ( !$res->numRows() ) {
+                               break;
+                       }
+
+                       // Collect the distinct comments from those rows
+                       $comments = [];
+                       foreach ( $res as $row ) {
+                               $comments[$row->$oldField] = 0;
+                       }
+                       $countComments += $this->loadCommentIDs( $dbw, $comments );
+
+                       // Update rows
+                       $inserts = [];
+                       $updates = [];
+                       foreach ( $res as $row ) {
+                               $inserts[] = [
+                                       $newPrimaryKey => $row->$primaryKey,
+                                       $newField => $comments[$row->$oldField]
+                               ];
+                               $updates[] = $row->$primaryKey;
+                       }
+                       $this->beginTransaction( $dbw, __METHOD__ );
+                       $dbw->insert( $newTable, $inserts, __METHOD__ );
+                       $dbw->update( $table, [ $oldField => '' ], [ $primaryKey => $updates ], __METHOD__ );
+                       $countUpdated += $dbw->affectedRows();
+                       $this->commitTransaction( $dbw, __METHOD__ );
+
+                       // Calculate the "next" condition
+                       $next = [ $primaryKey . ' > ' . $dbw->addQuotes( $row->$primaryKey ) ];
+                       $this->output( "... {$row->$primaryKey}\n" );
+               }
+
+               $this->output(
+                       "Completed migration, updated $countUpdated row(s) with $countComments new comment(s)\n"
+               );
+       }
+}
+
+$maintClass = "MigrateComments";
+require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/maintenance/oracle/archives/patch-auto_increment_triggers.sql b/maintenance/oracle/archives/patch-auto_increment_triggers.sql
new file mode 100644 (file)
index 0000000..6b471b0
--- /dev/null
@@ -0,0 +1,144 @@
+define mw_prefix='{$wgDBprefix}';
+
+-- Package to help with making Oracle more like other DBs with respect to
+-- auto-incrementing columns.
+/*$mw$*/
+CREATE PACKAGE &mw_prefix.lastval_pkg IS
+  lastval NUMBER;
+  PROCEDURE setLastval(val IN NUMBER, field OUT NUMBER);
+  FUNCTION getLastval RETURN NUMBER;
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE PACKAGE BODY &mw_prefix.lastval_pkg IS
+  PROCEDURE setLastval(val IN NUMBER, field OUT NUMBER) IS BEGIN
+    lastval := val;
+    field := val;
+  END;
+
+  FUNCTION getLastval RETURN NUMBER IS BEGIN
+    RETURN lastval;
+  END;
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.mwuser_default_user_id BEFORE INSERT ON &mw_prefix.mwuser
+       FOR EACH ROW WHEN (new.user_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(user_user_id_seq.nextval, :new.user_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.page_default_page_id BEFORE INSERT ON &mw_prefix.page
+       FOR EACH ROW WHEN (new.page_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(page_page_id_seq.nextval, :new.page_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.revision_default_rev_id BEFORE INSERT ON &mw_prefix.revision
+       FOR EACH ROW WHEN (new.rev_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(revision_rev_id_seq.nextval, :new.rev_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.text_default_old_id BEFORE INSERT ON &mw_prefix.text
+       FOR EACH ROW WHEN (new.old_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(text_old_id_seq.nextval, :new.old_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.archive_default_ar_id BEFORE INSERT ON &mw_prefix.archive
+       FOR EACH ROW WHEN (new.ar_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(archive_ar_id_seq.nextval, :new.ar_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.category_default_cat_id BEFORE INSERT ON &mw_prefix.category
+       FOR EACH ROW WHEN (new.cat_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(category_cat_id_seq.nextval, :new.cat_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.externallinks_default_el_id BEFORE INSERT ON &mw_prefix.externallinks
+       FOR EACH ROW WHEN (new.el_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(externallinks_el_id_seq.nextval, :new.el_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.ipblocks_default_ipb_id BEFORE INSERT ON &mw_prefix.ipblocks
+       FOR EACH ROW WHEN (new.ipb_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(ipblocks_ipb_id_seq.nextval, :new.ipb_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.filearchive_default_fa_id BEFORE INSERT ON &mw_prefix.filearchive
+       FOR EACH ROW WHEN (new.fa_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(filearchive_fa_id_seq.nextval, :new.fa_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.uploadstash_default_us_id BEFORE INSERT ON &mw_prefix.uploadstash
+       FOR EACH ROW WHEN (new.us_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(uploadstash_us_id_seq.nextval, :new.us_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.recentchanges_default_rc_id BEFORE INSERT ON &mw_prefix.recentchanges
+       FOR EACH ROW WHEN (new.rc_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(recentchanges_rc_id_seq.nextval, :new.rc_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.logging_default_log_id BEFORE INSERT ON &mw_prefix.logging
+       FOR EACH ROW WHEN (new.log_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(logging_log_id_seq.nextval, :new.log_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.job_default_job_id BEFORE INSERT ON &mw_prefix.job
+       FOR EACH ROW WHEN (new.job_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(job_job_id_seq.nextval, :new.job_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.page_restrictions_default_pr_id BEFORE INSERT ON &mw_prefix.page_restrictions
+       FOR EACH ROW WHEN (new.pr_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(page_restrictions_pr_id_seq.nextval, :new.pr_id);
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.sites_default_site_id BEFORE INSERT ON &mw_prefix.sites
+       FOR EACH ROW WHEN (new.site_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(sites_site_id_seq.nextval, :new.site_id);
+END;
+/*$mw$*/
index fc3c696..44c907c 100644 (file)
@@ -1,6 +1,28 @@
 -- defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
 define mw_prefix='{$wgDBprefix}';
 
+-- Package to help with making Oracle more like other DBs with respect to
+-- auto-incrementing columns.
+/*$mw$*/
+CREATE PACKAGE &mw_prefix.lastval_pkg IS
+  lastval NUMBER;
+  PROCEDURE setLastval(val IN NUMBER, field OUT NUMBER);
+  FUNCTION getLastval RETURN NUMBER;
+END;
+/*$mw$*/
+
+/*$mw$*/
+CREATE PACKAGE BODY &mw_prefix.lastval_pkg IS
+  PROCEDURE setLastval(val IN NUMBER, field OUT NUMBER) IS BEGIN
+    lastval := val;
+    field := val;
+  END;
+
+  FUNCTION getLastval RETURN NUMBER IS BEGIN
+    RETURN lastval;
+  END;
+END;
+/*$mw$*/
 
 CREATE SEQUENCE user_user_id_seq;
 CREATE TABLE &mw_prefix.mwuser ( -- replace reserved word 'user'
@@ -25,6 +47,13 @@ ALTER TABLE &mw_prefix.mwuser ADD CONSTRAINT &mw_prefix.mwuser_pk PRIMARY KEY (u
 CREATE UNIQUE INDEX &mw_prefix.mwuser_u01 ON &mw_prefix.mwuser (user_name);
 CREATE INDEX &mw_prefix.mwuser_i01 ON &mw_prefix.mwuser (user_email_token);
 CREATE INDEX &mw_prefix.mwuser_i02 ON &mw_prefix.mwuser (user_email, user_name);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.mwuser_default_user_id BEFORE INSERT ON &mw_prefix.mwuser
+       FOR EACH ROW WHEN (new.user_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(user_user_id_seq.nextval, :new.user_id);
+END;
+/*$mw$*/
 
 -- Create a dummy user to satisfy fk contraints especially with revisions
 INSERT INTO &mw_prefix.mwuser
@@ -86,6 +115,13 @@ CREATE UNIQUE INDEX &mw_prefix.page_u01 ON &mw_prefix.page (page_namespace,page_
 CREATE INDEX &mw_prefix.page_i01 ON &mw_prefix.page (page_random);
 CREATE INDEX &mw_prefix.page_i02 ON &mw_prefix.page (page_len);
 CREATE INDEX &mw_prefix.page_i03 ON &mw_prefix.page (page_is_redirect, page_namespace, page_len);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.page_default_page_id BEFORE INSERT ON &mw_prefix.page
+       FOR EACH ROW WHEN (new.page_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(page_page_id_seq.nextval, :new.page_id);
+END;
+/*$mw$*/
 
 -- Create a dummy page to satisfy fk contraints especially with revisions
 INSERT INTO &mw_prefix.page
@@ -125,6 +161,13 @@ CREATE INDEX &mw_prefix.revision_i02 ON &mw_prefix.revision (rev_page,rev_timest
 CREATE INDEX &mw_prefix.revision_i03 ON &mw_prefix.revision (rev_user,rev_timestamp);
 CREATE INDEX &mw_prefix.revision_i04 ON &mw_prefix.revision (rev_user_text,rev_timestamp);
 CREATE INDEX &mw_prefix.revision_i05 ON &mw_prefix.revision (rev_page,rev_user,rev_timestamp);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.revision_default_rev_id BEFORE INSERT ON &mw_prefix.revision
+       FOR EACH ROW WHEN (new.rev_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(revision_rev_id_seq.nextval, :new.rev_id);
+END;
+/*$mw$*/
 
 CREATE SEQUENCE text_old_id_seq;
 CREATE TABLE &mw_prefix.pagecontent ( -- replaces reserved word 'text'
@@ -133,6 +176,13 @@ CREATE TABLE &mw_prefix.pagecontent ( -- replaces reserved word 'text'
   old_flags  VARCHAR2(255)
 );
 ALTER TABLE &mw_prefix.pagecontent ADD CONSTRAINT &mw_prefix.pagecontent_pk PRIMARY KEY (old_id);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.text_default_old_id BEFORE INSERT ON &mw_prefix.text
+       FOR EACH ROW WHEN (new.old_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(text_old_id_seq.nextval, :new.old_id);
+END;
+/*$mw$*/
 
 CREATE SEQUENCE archive_ar_id_seq;
 CREATE TABLE &mw_prefix.archive (
@@ -161,6 +211,13 @@ ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_fk1 FOREIGN KEY
 CREATE INDEX &mw_prefix.archive_i01 ON &mw_prefix.archive (ar_namespace,ar_title,ar_timestamp);
 CREATE INDEX &mw_prefix.archive_i02 ON &mw_prefix.archive (ar_user_text,ar_timestamp);
 CREATE INDEX &mw_prefix.archive_i03 ON &mw_prefix.archive (ar_rev_id);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.archive_default_ar_id BEFORE INSERT ON &mw_prefix.archive
+       FOR EACH ROW WHEN (new.ar_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(archive_ar_id_seq.nextval, :new.ar_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.pagelinks (
   pl_from       NUMBER   NOT NULL,
@@ -215,6 +272,13 @@ CREATE TABLE &mw_prefix.category (
 ALTER TABLE &mw_prefix.category ADD CONSTRAINT &mw_prefix.category_pk PRIMARY KEY (cat_id);
 CREATE UNIQUE INDEX &mw_prefix.category_u01 ON &mw_prefix.category (cat_title);
 CREATE INDEX &mw_prefix.category_i01 ON &mw_prefix.category (cat_pages);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.category_default_cat_id BEFORE INSERT ON &mw_prefix.category
+       FOR EACH ROW WHEN (new.cat_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(category_cat_id_seq.nextval, :new.cat_id);
+END;
+/*$mw$*/
 
 CREATE SEQUENCE externallinks_el_id_seq;
 CREATE TABLE &mw_prefix.externallinks (
@@ -231,6 +295,13 @@ CREATE INDEX &mw_prefix.externallinks_i02 ON &mw_prefix.externallinks (el_to, el
 CREATE INDEX &mw_prefix.externallinks_i03 ON &mw_prefix.externallinks (el_index);
 CREATE INDEX &mw_prefix.externallinks_i04 ON &mw_prefix.externallinks (el_index_60, el_id);
 CREATE INDEX &mw_prefix.externallinks_i05 ON &mw_prefix.externallinks (el_from, el_index_60, el_id);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.externallinks_default_el_id BEFORE INSERT ON &mw_prefix.externallinks
+       FOR EACH ROW WHEN (new.el_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(externallinks_el_id_seq.nextval, :new.el_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.langlinks (
   ll_from    NUMBER  NOT NULL,
@@ -290,6 +361,13 @@ CREATE INDEX &mw_prefix.ipblocks_i02 ON &mw_prefix.ipblocks (ipb_range_start, ip
 CREATE INDEX &mw_prefix.ipblocks_i03 ON &mw_prefix.ipblocks (ipb_timestamp);
 CREATE INDEX &mw_prefix.ipblocks_i04 ON &mw_prefix.ipblocks (ipb_expiry);
 CREATE INDEX &mw_prefix.ipblocks_i05 ON &mw_prefix.ipblocks (ipb_parent_block_id);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.ipblocks_default_ipb_id BEFORE INSERT ON &mw_prefix.ipblocks
+       FOR EACH ROW WHEN (new.ipb_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(ipblocks_ipb_id_seq.nextval, :new.ipb_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.image (
   img_name         VARCHAR2(255)      NOT NULL,
@@ -374,6 +452,13 @@ CREATE INDEX &mw_prefix.filearchive_i02 ON &mw_prefix.filearchive (fa_storage_gr
 CREATE INDEX &mw_prefix.filearchive_i03 ON &mw_prefix.filearchive (fa_deleted_timestamp);
 CREATE INDEX &mw_prefix.filearchive_i04 ON &mw_prefix.filearchive (fa_user_text,fa_timestamp);
 CREATE INDEX &mw_prefix.filearchive_i05 ON &mw_prefix.filearchive (fa_sha1);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.filearchive_default_fa_id BEFORE INSERT ON &mw_prefix.filearchive
+       FOR EACH ROW WHEN (new.fa_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(filearchive_fa_id_seq.nextval, :new.fa_id);
+END;
+/*$mw$*/
 
 CREATE SEQUENCE uploadstash_us_id_seq;
 CREATE TABLE &mw_prefix.uploadstash (
@@ -400,6 +485,13 @@ ALTER TABLE &mw_prefix.uploadstash ADD CONSTRAINT &mw_prefix.uploadstash_fk1 FOR
 CREATE INDEX &mw_prefix.uploadstash_i01 ON &mw_prefix.uploadstash (us_user);
 CREATE INDEX &mw_prefix.uploadstash_i02 ON &mw_prefix.uploadstash (us_timestamp);
 CREATE UNIQUE INDEX &mw_prefix.uploadstash_u01 ON &mw_prefix.uploadstash (us_key);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.uploadstash_default_us_id BEFORE INSERT ON &mw_prefix.uploadstash
+       FOR EACH ROW WHEN (new.us_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(uploadstash_us_id_seq.nextval, :new.us_id);
+END;
+/*$mw$*/
 
 CREATE SEQUENCE recentchanges_rc_id_seq;
 CREATE TABLE &mw_prefix.recentchanges (
@@ -440,6 +532,13 @@ CREATE INDEX &mw_prefix.recentchanges_i05 ON &mw_prefix.recentchanges (rc_ip);
 CREATE INDEX &mw_prefix.recentchanges_i06 ON &mw_prefix.recentchanges (rc_namespace, rc_user_text);
 CREATE INDEX &mw_prefix.recentchanges_i07 ON &mw_prefix.recentchanges (rc_user_text, rc_timestamp);
 CREATE INDEX &mw_prefix.recentchanges_i08 ON &mw_prefix.recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.recentchanges_default_rc_id BEFORE INSERT ON &mw_prefix.recentchanges
+       FOR EACH ROW WHEN (new.rc_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(recentchanges_rc_id_seq.nextval, :new.rc_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.watchlist (
   wl_id                     NUMBER     NOT NULL,
@@ -518,6 +617,13 @@ CREATE INDEX &mw_prefix.logging_i04 ON &mw_prefix.logging (log_timestamp);
 CREATE INDEX &mw_prefix.logging_i05 ON &mw_prefix.logging (log_type, log_action, log_timestamp);
 CREATE INDEX &mw_prefix.logging_i06 ON &mw_prefix.logging (log_user_text, log_type, log_timestamp);
 CREATE INDEX &mw_prefix.logging_i07 ON &mw_prefix.logging (log_user_text, log_timestamp);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.logging_default_log_id BEFORE INSERT ON &mw_prefix.logging
+       FOR EACH ROW WHEN (new.log_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(logging_log_id_seq.nextval, :new.log_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.log_search (
   ls_field VARCHAR2(32) NOT NULL,
@@ -548,6 +654,13 @@ CREATE INDEX &mw_prefix.job_i02 ON &mw_prefix.job (job_timestamp);
 CREATE INDEX &mw_prefix.job_i03 ON &mw_prefix.job (job_sha1);
 CREATE INDEX &mw_prefix.job_i04 ON &mw_prefix.job (job_cmd,job_token,job_random);
 CREATE INDEX &mw_prefix.job_i05 ON &mw_prefix.job (job_attempts);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.job_default_job_id BEFORE INSERT ON &mw_prefix.job
+       FOR EACH ROW WHEN (new.job_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(job_job_id_seq.nextval, :new.job_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.querycache_info (
   qci_type       VARCHAR2(32) NOT NULL,
@@ -593,6 +706,13 @@ CREATE UNIQUE INDEX &mw_prefix.page_restrictions_u01 ON &mw_prefix.page_restrict
 CREATE INDEX &mw_prefix.page_restrictions_i01 ON &mw_prefix.page_restrictions (pr_type,pr_level);
 CREATE INDEX &mw_prefix.page_restrictions_i02 ON &mw_prefix.page_restrictions (pr_level);
 CREATE INDEX &mw_prefix.page_restrictions_i03 ON &mw_prefix.page_restrictions (pr_cascade);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.page_restrictions_default_pr_id BEFORE INSERT ON &mw_prefix.page_restrictions
+       FOR EACH ROW WHEN (new.pr_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(page_restrictions_pr_id_seq.nextval, :new.pr_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.protected_titles (
   pt_namespace   NUMBER           DEFAULT 0 NOT NULL,
@@ -701,6 +821,13 @@ CREATE INDEX &mw_prefix.sites_i04 ON &mw_prefix.sites (site_language);
 CREATE INDEX &mw_prefix.sites_i05 ON &mw_prefix.sites (site_protocol);
 CREATE INDEX &mw_prefix.sites_i06 ON &mw_prefix.sites (site_domain);
 CREATE INDEX &mw_prefix.sites_i07 ON &mw_prefix.sites (site_forward);
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.sites_default_site_id BEFORE INSERT ON &mw_prefix.sites
+       FOR EACH ROW WHEN (new.site_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(sites_site_id_seq.nextval, :new.site_id);
+END;
+/*$mw$*/
 
 CREATE TABLE &mw_prefix.site_identifiers (
   si_site NUMBER NOT NULL,
index e36c5b6..644fb95 100644 (file)
@@ -75,20 +75,24 @@ class Orphans extends Maintenance {
         */
        private function checkOrphans( $fix ) {
                $dbw = $this->getDB( DB_MASTER );
-               $page = $dbw->tableName( 'page' );
-               $revision = $dbw->tableName( 'revision' );
+               $commentStore = new CommentStore( 'rev_comment' );
 
                if ( $fix ) {
                        $this->lockTables( $dbw );
                }
 
+               $commentQuery = $commentStore->getJoin();
+
                $this->output( "Checking for orphan revision table entries... "
                        . "(this may take a while on a large wiki)\n" );
-               $result = $dbw->query( "
-                       SELECT *
-                       FROM $revision LEFT OUTER JOIN $page ON rev_page=page_id
-                       WHERE page_id IS NULL
-               " );
+               $result = $dbw->select(
+                       [ 'revision', 'page' ] + $commentQuery['tables'],
+                       [ 'rev_id', 'rev_page', 'rev_timestamp', 'rev_user_text' ] + $commentQuery['fields'],
+                       [ 'page_id' => null ],
+                       __METHOD__,
+                       [],
+                       [ 'page' => [ 'LEFT JOIN', [ 'rev_page=page_id' ] ] ] + $commentQuery['joins']
+               );
                $orphans = $result->numRows();
                if ( $orphans > 0 ) {
                        global $wgContLang;
@@ -100,9 +104,10 @@ class Orphans extends Maintenance {
                        ) );
 
                        foreach ( $result as $row ) {
-                               $comment = ( $row->rev_comment == '' )
-                                       ? ''
-                                       : '(' . $wgContLang->truncate( $row->rev_comment, 40 ) . ')';
+                               $comment = $commentStore->getComment( $row )->text;
+                               if ( $comment !== '' ) {
+                                       $comment = '(' . $wgContLang->truncate( $comment, 40 ) . ')';
+                               }
                                $this->output( sprintf( "%10d %10d %14s %20s %s\n",
                                        $row->rev_id,
                                        $row->rev_page,
diff --git a/maintenance/postgres/archives/patch-comment-table.sql b/maintenance/postgres/archives/patch-comment-table.sql
new file mode 100644 (file)
index 0000000..243a3b3
--- /dev/null
@@ -0,0 +1,27 @@
+--
+-- patch-comment-table.sql
+--
+-- T166732. Add a `comment` table, and temporary tables to reference it.
+
+CREATE SEQUENCE comment_comment_id_seq;
+CREATE TABLE comment (
+       comment_id   INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('comment_comment_id_seq'),
+       comment_hash INTEGER NOT NULL,
+       comment_text TEXT    NOT NULL,
+       comment_data TEXT
+);
+CREATE INDEX comment_hash ON comment (comment_hash);
+
+CREATE TABLE revision_comment_temp (
+       revcomment_rev        INTEGER NOT NULL,
+       revcomment_comment_id INTEGER NOT NULL,
+       PRIMARY KEY (revcomment_rev, revcomment_comment_id)
+);
+CREATE UNIQUE INDEX revcomment_rev ON revision_comment_temp (revcomment_rev);
+
+CREATE TABLE image_comment_temp (
+       imgcomment_name       TEXT NOT NULL,
+       imgcomment_description_id INTEGER NOT NULL,
+       PRIMARY KEY (imgcomment_name, imgcomment_description_id)
+);
+CREATE UNIQUE INDEX imgcomment_name ON image_comment_temp (imgcomment_name);
index 03fd03a..eea9e68 100644 (file)
@@ -12,6 +12,7 @@ SET client_min_messages = 'ERROR';
 DROP SEQUENCE IF EXISTS user_user_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS page_page_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS revision_rev_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS comment_comment_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS text_old_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS page_restrictions_pr_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS ipblocks_ipb_id_seq CASCADE;
@@ -132,7 +133,7 @@ CREATE TABLE revision (
   rev_id             INTEGER      NOT NULL  UNIQUE DEFAULT nextval('revision_rev_id_seq'),
   rev_page           INTEGER          NULL  REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
   rev_text_id        INTEGER          NULL, -- FK
-  rev_comment        TEXT,
+  rev_comment        TEXT         NOT NULL DEFAULT '',
   rev_user           INTEGER      NOT NULL  REFERENCES mwuser(user_id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
   rev_user_text      TEXT         NOT NULL,
   rev_timestamp      TIMESTAMPTZ  NOT NULL,
@@ -150,6 +151,12 @@ CREATE INDEX rev_timestamp_idx      ON revision (rev_timestamp);
 CREATE INDEX rev_user_idx           ON revision (rev_user);
 CREATE INDEX rev_user_text_idx      ON revision (rev_user_text);
 
+CREATE TABLE revision_comment_temp (
+       revcomment_rev        INTEGER NOT NULL,
+       revcomment_comment_id INTEGER NOT NULL,
+       PRIMARY KEY (revcomment_rev, revcomment_comment_id)
+);
+CREATE UNIQUE INDEX revcomment_rev ON revision_comment_temp (revcomment_rev);
 
 CREATE SEQUENCE text_old_id_seq;
 CREATE TABLE pagecontent ( -- replaces reserved word 'text'
@@ -159,6 +166,16 @@ CREATE TABLE pagecontent ( -- replaces reserved word 'text'
 );
 
 
+CREATE SEQUENCE comment_comment_id_seq;
+CREATE TABLE comment (
+  comment_id   INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('comment_comment_id_seq'),
+  comment_hash INTEGER NOT NULL,
+  comment_text TEXT    NOT NULL,
+  comment_data TEXT
+);
+CREATE INDEX comment_hash ON comment (comment_hash);
+
+
 CREATE SEQUENCE page_restrictions_pr_id_seq;
 CREATE TABLE page_restrictions (
   pr_id      INTEGER      NOT NULL  UNIQUE DEFAULT nextval('page_restrictions_pr_id_seq'),
@@ -191,7 +208,8 @@ CREATE TABLE archive (
   ar_page_id        INTEGER          NULL,
   ar_parent_id      INTEGER          NULL,
   ar_sha1           TEXT         NOT NULL DEFAULT '',
-  ar_comment        TEXT,
+  ar_comment        TEXT         NOT NULL DEFAULT '',
+  ar_comment_id     INTEGER      NOT NULL DEFAULT 0,
   ar_user           INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
   ar_user_text      TEXT         NOT NULL,
   ar_timestamp      TIMESTAMPTZ  NOT NULL,
@@ -296,7 +314,8 @@ CREATE TABLE ipblocks (
   ipb_user              INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
   ipb_by                INTEGER      NOT NULL  REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
   ipb_by_text           TEXT         NOT NULL  DEFAULT '',
-  ipb_reason            TEXT         NOT NULL,
+  ipb_reason            TEXT         NOT NULL  DEFAULT '',
+  ipb_reason_id         INTEGER      NOT NULL  DEFAULT 0,
   ipb_timestamp         TIMESTAMPTZ  NOT NULL,
   ipb_auto              SMALLINT     NOT NULL  DEFAULT 0,
   ipb_anon_only         SMALLINT     NOT NULL  DEFAULT 0,
@@ -327,7 +346,7 @@ CREATE TABLE image (
   img_media_type   TEXT,
   img_major_mime   TEXT                DEFAULT 'unknown',
   img_minor_mime   TEXT                DEFAULT 'unknown',
-  img_description  TEXT      NOT NULL,
+  img_description  TEXT      NOT NULL  DEFAULT '',
   img_user         INTEGER       NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
   img_user_text    TEXT      NOT NULL,
   img_timestamp    TIMESTAMPTZ,
@@ -337,6 +356,13 @@ CREATE INDEX img_size_idx      ON image (img_size);
 CREATE INDEX img_timestamp_idx ON image (img_timestamp);
 CREATE INDEX img_sha1          ON image (img_sha1);
 
+CREATE TABLE image_comment_temp (
+       imgcomment_name       TEXT NOT NULL,
+       imgcomment_description_id INTEGER NOT NULL,
+       PRIMARY KEY (imgcomment_name, imgcomment_description_id)
+);
+CREATE UNIQUE INDEX imgcomment_name ON image_comment_temp (imgcomment_name);
+
 CREATE TABLE oldimage (
   oi_name          TEXT         NOT NULL,
   oi_archive_name  TEXT         NOT NULL,
@@ -344,7 +370,8 @@ CREATE TABLE oldimage (
   oi_width         INTEGER      NOT NULL,
   oi_height        INTEGER      NOT NULL,
   oi_bits          SMALLINT         NULL,
-  oi_description   TEXT,
+  oi_description   TEXT         NOT NULL DEFAULT '',
+  oi_description_id INTEGER     NOT NULL DEFAULT 0,
   oi_user          INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
   oi_user_text     TEXT         NOT NULL,
   oi_timestamp     TIMESTAMPTZ      NULL,
@@ -370,7 +397,8 @@ CREATE TABLE filearchive (
   fa_storage_key        TEXT,
   fa_deleted_user       INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
   fa_deleted_timestamp  TIMESTAMPTZ  NOT NULL,
-  fa_deleted_reason     TEXT,
+  fa_deleted_reason     TEXT         NOT NULL  DEFAULT '',
+  fa_deleted_reason_id  INTEGER      NOT NULL  DEFAULT 0,
   fa_size               INTEGER      NOT NULL,
   fa_width              INTEGER      NOT NULL,
   fa_height             INTEGER      NOT NULL,
@@ -379,7 +407,8 @@ CREATE TABLE filearchive (
   fa_media_type         TEXT,
   fa_major_mime         TEXT                   DEFAULT 'unknown',
   fa_minor_mime         TEXT                   DEFAULT 'unknown',
-  fa_description        TEXT         NOT NULL,
+  fa_description        TEXT         NOT NULL DEFAULT '',
+  fa_description_id     INTEGER      NOT NULL DEFAULT 0,
   fa_user               INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
   fa_user_text          TEXT         NOT NULL,
   fa_timestamp          TIMESTAMPTZ,
@@ -429,7 +458,8 @@ CREATE TABLE recentchanges (
   rc_user_text       TEXT         NOT NULL,
   rc_namespace       SMALLINT     NOT NULL,
   rc_title           TEXT         NOT NULL,
-  rc_comment         TEXT,
+  rc_comment         TEXT         NOT NULL  DEFAULT '',
+  rc_comment_id      INTEGER      NOT NULL  DEFAULT 0,
   rc_minor           SMALLINT     NOT NULL  DEFAULT 0,
   rc_bot             SMALLINT     NOT NULL  DEFAULT 0,
   rc_new             SMALLINT     NOT NULL  DEFAULT 0,
@@ -528,7 +558,8 @@ CREATE TABLE logging (
   log_user        INTEGER                REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
   log_namespace   SMALLINT     NOT NULL,
   log_title       TEXT         NOT NULL,
-  log_comment     TEXT,
+  log_comment     TEXT         NOT NULL DEFAULT '',
+  log_comment_id  INTEGER      NOT NULL DEFAULT 0,
   log_params      TEXT,
   log_deleted     SMALLINT     NOT NULL DEFAULT 0,
   log_user_text   TEXT         NOT NULL DEFAULT '',
@@ -635,7 +666,8 @@ CREATE TABLE protected_titles (
   pt_namespace   SMALLINT    NOT NULL,
   pt_title       TEXT        NOT NULL,
   pt_user        INTEGER         NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  pt_reason      TEXT            NULL,
+  pt_reason      TEXT        NOT NULL DEFAULT '',
+  pt_reason_id   INTEGER     NOT NULL DEFAULT 0,
   pt_timestamp   TIMESTAMPTZ NOT NULL,
   pt_expiry      TIMESTAMPTZ     NULL,
   pt_create_perm TEXT        NOT NULL DEFAULT ''
index b93d112..a2cf3c5 100644 (file)
@@ -80,6 +80,8 @@ class RebuildRecentchanges extends Maintenance {
         */
        private function rebuildRecentChangesTablePass1() {
                $dbw = $this->getDB( DB_MASTER );
+               $revCommentStore = new CommentStore( 'rev_comment' );
+               $rcCommentStore = new CommentStore( 'rc_comment' );
 
                if ( $this->hasOption( 'from' ) && $this->hasOption( 'to' ) ) {
                        $this->cutoffFrom = wfTimestamp( TS_UNIX, $this->getOption( 'from' ) );
@@ -113,13 +115,14 @@ class RebuildRecentchanges extends Maintenance {
                }
 
                $this->output( "Loading from page and revision tables...\n" );
+
+               $commentQuery = $revCommentStore->getJoin();
                $res = $dbw->select(
-                       [ 'page', 'revision' ],
+                       [ 'revision', 'page' ] + $commentQuery['tables'],
                        [
                                'rev_timestamp',
                                'rev_user',
                                'rev_user_text',
-                               'rev_comment',
                                'rev_minor_edit',
                                'rev_id',
                                'rev_deleted',
@@ -127,19 +130,22 @@ class RebuildRecentchanges extends Maintenance {
                                'page_title',
                                'page_is_new',
                                'page_id'
-                       ],
+                       ] + $commentQuery['fields'],
                        [
                                'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
-                               'rev_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
-                               'rev_page=page_id'
+                               'rev_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
                        ],
                        __METHOD__,
-                       [ 'ORDER BY' => 'rev_timestamp DESC' ]
+                       [ 'ORDER BY' => 'rev_timestamp DESC' ],
+                       [
+                               'page' => [ 'JOIN', 'rev_page=page_id' ],
+                       ] + $commentQuery['joins']
                );
 
                $this->output( "Inserting from page and revision tables...\n" );
                $inserted = 0;
                foreach ( $res as $row ) {
+                       $comment = $revCommentStore->getComment( $row );
                        $dbw->insert(
                                'recentchanges',
                                [
@@ -148,7 +154,6 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_user_text' => $row->rev_user_text,
                                        'rc_namespace' => $row->page_namespace,
                                        'rc_title' => $row->page_title,
-                                       'rc_comment' => $row->rev_comment,
                                        'rc_minor' => $row->rev_minor_edit,
                                        'rc_bot' => 0,
                                        'rc_new' => $row->page_is_new,
@@ -156,10 +161,9 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_this_oldid' => $row->rev_id,
                                        'rc_last_oldid' => 0, // is this ok?
                                        'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT,
-                                       'rc_source' => $row->page_is_new ? RecentChange::SRC_NEW : RecentChange::SRC_EDIT
-                                       ,
+                                       'rc_source' => $row->page_is_new ? RecentChange::SRC_NEW : RecentChange::SRC_EDIT,
                                        'rc_deleted' => $row->rev_deleted
-                               ],
+                               ] + $rcCommentStore->insert( $dbw, $comment ),
                                __METHOD__
                        );
                        if ( ( ++$inserted % $this->mBatchSize ) == 0 ) {
@@ -266,25 +270,27 @@ class RebuildRecentchanges extends Maintenance {
                global $wgLogTypes, $wgLogRestrictions;
 
                $dbw = $this->getDB( DB_MASTER );
+               $logCommentStore = new CommentStore( 'log_comment' );
+               $rcCommentStore = new CommentStore( 'rc_comment' );
 
                $this->output( "Loading from user, page, and logging tables...\n" );
 
+               $commentQuery = $logCommentStore->getJoin();
                $res = $dbw->select(
-                       [ 'user', 'logging', 'page' ],
+                       [ 'user', 'logging', 'page' ] + $commentQuery['tables'],
                        [
                                'log_timestamp',
                                'log_user',
                                'user_name',
                                'log_namespace',
                                'log_title',
-                               'log_comment',
                                'page_id',
                                'log_type',
                                'log_action',
                                'log_id',
                                'log_params',
                                'log_deleted'
-                       ],
+                       ] + $commentQuery['fields'],
                        [
                                'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
                                'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
@@ -298,13 +304,14 @@ class RebuildRecentchanges extends Maintenance {
                        [
                                'page' =>
                                        [ 'LEFT JOIN', [ 'log_namespace=page_namespace', 'log_title=page_title' ] ]
-                       ]
+                       ] + $commentQuery['joins']
                );
 
                $field = $dbw->fieldInfo( 'recentchanges', 'rc_cur_id' );
 
                $inserted = 0;
                foreach ( $res as $row ) {
+                       $comment = $logCommentStore->getComment( $row );
                        $dbw->insert(
                                'recentchanges',
                                [
@@ -313,7 +320,6 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_user_text' => $row->user_name,
                                        'rc_namespace' => $row->log_namespace,
                                        'rc_title' => $row->log_title,
-                                       'rc_comment' => $row->log_comment,
                                        'rc_minor' => 0,
                                        'rc_bot' => 0,
                                        'rc_patrolled' => 1,
@@ -330,7 +336,7 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_logid' => $row->log_id,
                                        'rc_params' => $row->log_params,
                                        'rc_deleted' => $row->log_deleted
-                               ],
+                               ] + $rcCommentStore->insert( $dbw, $comment ),
                                __METHOD__
                        );
 
diff --git a/maintenance/sqlite/archives/patch-categorylinks-fix-pk.sql b/maintenance/sqlite/archives/patch-categorylinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..13a75a3
--- /dev/null
@@ -0,0 +1,60 @@
+CREATE TABLE /*_*/categorylinks_tmp (
+  -- Key to page_id of the page defined as a category member.
+  cl_from int unsigned NOT NULL default 0,
+
+  -- Name of the category.
+  -- This is also the page_title of the category's description page;
+  -- all such pages are in namespace 14 (NS_CATEGORY).
+  cl_to varchar(255) binary NOT NULL default '',
+
+  -- A binary string obtained by applying a sortkey generation algorithm
+  -- (Collation::getSortKey()) to page_title, or cl_sortkey_prefix . "\n"
+  -- . page_title if cl_sortkey_prefix is nonempty.
+  cl_sortkey varbinary(230) NOT NULL default '',
+
+  -- A prefix for the raw sortkey manually specified by the user, either via
+  -- [[Category:Foo|prefix]] or {{defaultsort:prefix}}.  If nonempty, it's
+  -- concatenated with a line break followed by the page title before the sortkey
+  -- conversion algorithm is run.  We store this so that we can update
+  -- collations without reparsing all pages.
+  -- Note: If you change the length of this field, you also need to change
+  -- code in LinksUpdate.php. See T27254.
+  cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+
+  -- This isn't really used at present. Provided for an optional
+  -- sorting method by approximate addition time.
+  cl_timestamp timestamp NOT NULL,
+
+  -- Stores $wgCategoryCollation at the time cl_sortkey was generated.  This
+  -- can be used to install new collation versions, tracking which rows are not
+  -- yet updated.  '' means no collation, this is a legacy row that needs to be
+  -- updated by updateCollation.php.  In the future, it might be possible to
+  -- specify different collations per category.
+  cl_collation varbinary(32) NOT NULL default '',
+
+  -- Stores whether cl_from is a category, file, or other page, so we can
+  -- paginate the three categories separately.  This never has to be updated
+  -- after the page is created, since none of these page types can be moved to
+  -- any other.
+  cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page',
+  PRIMARY KEY (cl_from,cl_to)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/categorylinks_tmp
+       SELECT *
+               FROM /*_*/categorylinks;
+
+DROP TABLE /*_*/categorylinks;
+
+ALTER TABLE /*_*/categorylinks_tmp RENAME TO /*_*/categorylinks;
+
+-- We always sort within a given category, and within a given type.  FIXME:
+-- Formerly this index didn't cover cl_type (since that didn't exist), so old
+-- callers won't be using an index: fix this?
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+
+-- Used by the API (and some extensions)
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+
+-- Used when updating collation (e.g. updateCollation.php)
+CREATE INDEX /*i*/cl_collation_ext ON /*_*/categorylinks (cl_collation, cl_to, cl_type, cl_from);
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-comment-table.sql b/maintenance/sqlite/archives/patch-comment-table.sql
new file mode 100644 (file)
index 0000000..f743b55
--- /dev/null
@@ -0,0 +1,332 @@
+--
+-- patch-comment-table.sql
+--
+-- T166732. Add a `comment` table and various columns (and temporary tables) to reference it.
+-- Sigh, sqlite, such trouble just to change the default value of a column.
+
+CREATE TABLE /*_*/comment (
+  comment_id bigint unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  comment_hash INT NOT NULL,
+  comment_text BLOB NOT NULL,
+  comment_data BLOB
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/comment_hash ON /*_*/comment (comment_hash);
+
+CREATE TABLE /*_*/revision_comment_temp (
+  revcomment_rev int unsigned NOT NULL,
+  revcomment_comment_id bigint unsigned NOT NULL,
+  PRIMARY KEY (revcomment_rev, revcomment_comment_id)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/revcomment_rev ON /*_*/revision_comment_temp (revcomment_rev);
+
+CREATE TABLE /*_*/image_comment_temp (
+  imgcomment_name varchar(255) binary NOT NULL,
+  imgcomment_description_id bigint unsigned NOT NULL,
+  PRIMARY KEY (imgcomment_name, imgcomment_description_id)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/imgcomment_name ON /*_*/image_comment_temp (imgcomment_name);
+
+ALTER TABLE /*_*/recentchanges
+  ADD COLUMN rc_comment_id bigint unsigned NOT NULL DEFAULT 0;
+
+ALTER TABLE /*_*/logging
+  ADD COLUMN log_comment_id bigint unsigned NOT NULL DEFAULT 0;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/revision_tmp;
+CREATE TABLE /*_*/revision_tmp (
+  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rev_page int unsigned NOT NULL,
+  rev_text_id int unsigned NOT NULL,
+  rev_comment varbinary(767) NOT NULL default '',
+  rev_user int unsigned NOT NULL default 0,
+  rev_user_text varchar(255) binary NOT NULL default '',
+  rev_timestamp binary(14) NOT NULL default '',
+  rev_minor_edit tinyint unsigned NOT NULL default 0,
+  rev_deleted tinyint unsigned NOT NULL default 0,
+  rev_len int unsigned,
+  rev_parent_id int unsigned default NULL,
+  rev_sha1 varbinary(32) NOT NULL default '',
+  rev_content_model varbinary(32) DEFAULT NULL,
+  rev_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+
+INSERT OR IGNORE INTO /*_*/revision_tmp (
+       rev_id, rev_page, rev_text_id, rev_comment, rev_user, rev_user_text,
+       rev_timestamp, rev_minor_edit, rev_deleted, rev_len, rev_parent_id,
+       rev_sha1, rev_content_model, rev_content_format)
+ SELECT
+       rev_id, rev_page, rev_text_id, rev_comment, rev_user, rev_user_text,
+       rev_timestamp, rev_minor_edit, rev_deleted, rev_len, rev_parent_id,
+       rev_sha1, rev_content_model, rev_content_format
+  FROM /*_*/revision;
+
+DROP TABLE /*_*/revision;
+ALTER TABLE /*_*/revision_tmp RENAME TO /*_*/revision;
+CREATE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/archive_tmp;
+CREATE TABLE /*_*/archive_tmp (
+  ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ar_namespace int NOT NULL default 0,
+  ar_title varchar(255) binary NOT NULL default '',
+  ar_text mediumblob NOT NULL,
+  ar_comment varbinary(767) NOT NULL default '',
+  ar_comment_id bigint unsigned NOT NULL DEFAULT 0,
+  ar_user int unsigned NOT NULL default 0,
+  ar_user_text varchar(255) binary NOT NULL,
+  ar_timestamp binary(14) NOT NULL default '',
+  ar_minor_edit tinyint NOT NULL default 0,
+  ar_flags tinyblob NOT NULL,
+  ar_rev_id int unsigned,
+  ar_text_id int unsigned,
+  ar_deleted tinyint unsigned NOT NULL default 0,
+  ar_len int unsigned,
+  ar_page_id int unsigned,
+  ar_parent_id int unsigned default NULL,
+  ar_sha1 varbinary(32) NOT NULL default '',
+  ar_content_model varbinary(32) DEFAULT NULL,
+  ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/archive_tmp (
+       ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_user, ar_user_text,
+       ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted,
+       ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model,
+       ar_content_format)
+  SELECT
+       ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_user, ar_user_text,
+       ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted,
+       ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model,
+       ar_content_format
+  FROM /*_*/archive;
+
+DROP TABLE /*_*/archive;
+ALTER TABLE /*_*/archive_tmp RENAME TO /*_*/archive;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS ipblocks_tmp;
+CREATE TABLE /*_*/ipblocks_tmp (
+  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ipb_address tinyblob NOT NULL,
+  ipb_user int unsigned NOT NULL default 0,
+  ipb_by int unsigned NOT NULL default 0,
+  ipb_by_text varchar(255) binary NOT NULL default '',
+  ipb_reason varbinary(767) NOT NULL default '',
+  ipb_reason_id bigint unsigned NOT NULL DEFAULT 0,
+  ipb_timestamp binary(14) NOT NULL default '',
+  ipb_auto bool NOT NULL default 0,
+  ipb_anon_only bool NOT NULL default 0,
+  ipb_create_account bool NOT NULL default 1,
+  ipb_enable_autoblock bool NOT NULL default '1',
+  ipb_expiry varbinary(14) NOT NULL default '',
+  ipb_range_start tinyblob NOT NULL,
+  ipb_range_end tinyblob NOT NULL,
+  ipb_deleted bool NOT NULL default 0,
+  ipb_block_email bool NOT NULL default 0,
+  ipb_allow_usertalk bool NOT NULL default 0,
+  ipb_parent_block_id int default NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/ipblocks_tmp (
+       ipb_id, ipb_address, ipb_user, ipb_by, ipb_by_text, ipb_reason,
+       ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+       ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+       ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id)
+  SELECT
+       ipb_id, ipb_address, ipb_user, ipb_by, ipb_by_text, ipb_reason,
+       ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+       ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+       ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id
+  FROM /*_*/ipblocks;
+
+DROP TABLE /*_*/ipblocks;
+ALTER TABLE /*_*/ipblocks_tmp RENAME TO /*_*/ipblocks;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/image_tmp;
+CREATE TABLE /*_*/image_tmp (
+  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+  img_size int unsigned NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata mediumblob NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+  img_minor_mime varbinary(100) NOT NULL default "unknown",
+  img_description varbinary(767) NOT NULL default '',
+  img_user int unsigned NOT NULL default 0,
+  img_user_text varchar(255) binary NOT NULL,
+  img_timestamp varbinary(14) NOT NULL default '',
+  img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/image_tmp (
+       img_name, img_size, img_width, img_height, img_metadata, img_bits,
+       img_media_type, img_major_mime, img_minor_mime, img_description, img_user,
+       img_user_text, img_timestamp, img_sha1)
+  SELECT
+       img_name, img_size, img_width, img_height, img_metadata, img_bits,
+       img_media_type, img_major_mime, img_minor_mime, img_description, img_user,
+       img_user_text, img_timestamp, img_sha1
+  FROM /*_*/image;
+
+DROP TABLE /*_*/image;
+ALTER TABLE /*_*/image_tmp RENAME TO /*_*/image;
+CREATE INDEX /*i*/img_user_timestamp ON /*_*/image (img_user,img_timestamp);
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/oldimage_tmp;
+CREATE TABLE /*_*/oldimage_tmp (
+  oi_name varchar(255) binary NOT NULL default '',
+  oi_archive_name varchar(255) binary NOT NULL default '',
+  oi_size int unsigned NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description varbinary(767) NOT NULL default '',
+  oi_description_id bigint unsigned NOT NULL DEFAULT 0,
+  oi_user int unsigned NOT NULL default 0,
+  oi_user_text varchar(255) binary NOT NULL,
+  oi_timestamp binary(14) NOT NULL default '',
+  oi_metadata mediumblob NOT NULL,
+  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+  oi_minor_mime varbinary(100) NOT NULL default "unknown",
+  oi_deleted tinyint unsigned NOT NULL default 0,
+  oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/oldimage_tmp (
+       oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+       oi_description, oi_user, oi_user_text, oi_timestamp, oi_metadata,
+       oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1)
+  SELECT
+       oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+       oi_description, oi_user, oi_user_text, oi_timestamp, oi_metadata,
+       oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1
+  FROM /*_*/oldimage;
+
+DROP TABLE /*_*/oldimage;
+ALTER TABLE /*_*/oldimage_tmp RENAME TO /*_*/oldimage;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1(10));
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/filearchive_tmp;
+CREATE TABLE /*_*/filearchive_tmp (
+  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  fa_name varchar(255) binary NOT NULL default '',
+  fa_archive_name varchar(255) binary default '',
+  fa_storage_group varbinary(16),
+  fa_storage_key varbinary(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp binary(14) default '',
+  fa_deleted_reason varbinary(767) default '',
+  fa_deleted_reason_id bigint unsigned NOT NULL DEFAULT 0,
+  fa_size int unsigned default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata mediumblob,
+  fa_bits int default 0,
+  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown",
+  fa_minor_mime varbinary(100) default "unknown",
+  fa_description varbinary(767) default '',
+  fa_description_id bigint unsigned NOT NULL DEFAULT 0,
+  fa_user int unsigned default 0,
+  fa_user_text varchar(255) binary,
+  fa_timestamp binary(14) default '',
+  fa_deleted tinyint unsigned NOT NULL default 0,
+  fa_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/filearchive_tmp (
+       fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+       fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason, fa_size,
+       fa_width, fa_height, fa_metadata, fa_bits, fa_media_type, fa_major_mime,
+       fa_minor_mime, fa_description, fa_user, fa_user_text, fa_timestamp,
+       fa_deleted, fa_sha1)
+  SELECT
+       fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+       fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason, fa_size,
+       fa_width, fa_height, fa_metadata, fa_bits, fa_media_type, fa_major_mime,
+       fa_minor_mime, fa_description, fa_user, fa_user_text, fa_timestamp,
+       fa_deleted, fa_sha1
+  FROM /*_*/filearchive;
+
+DROP TABLE /*_*/filearchive;
+ALTER TABLE /*_*/filearchive_tmp RENAME TO /*_*/filearchive;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/protected_titles_tmp;
+CREATE TABLE /*_*/protected_titles_tmp (
+  pt_namespace int NOT NULL,
+  pt_title varchar(255) binary NOT NULL,
+  pt_user int unsigned NOT NULL,
+  pt_reason varbinary(767) default '',
+  pt_reason_id bigint unsigned NOT NULL DEFAULT 0,
+  pt_timestamp binary(14) NOT NULL,
+  pt_expiry varbinary(14) NOT NULL default '',
+  pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/protected_titles_tmp (
+       pt_namespace, pt_title, pt_user, pt_reason, pt_timestamp, pt_expiry, pt_create_perm)
+  SELECT
+       pt_namespace, pt_title, pt_user, pt_reason, pt_timestamp, pt_expiry, pt_create_perm
+  FROM /*_*/protected_titles;
+
+DROP TABLE /*_*/protected_titles;
+ALTER TABLE /*_*/protected_titles_tmp RENAME TO /*_*/protected_titles;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+
+COMMIT;
diff --git a/maintenance/sqlite/archives/patch-imagelinks-fix-pk.sql b/maintenance/sqlite/archives/patch-imagelinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..b48bea5
--- /dev/null
@@ -0,0 +1,25 @@
+CREATE TABLE /*_*/imagelinks_tmp (
+  -- Key to page_id of the page containing the image / media link.
+  il_from int unsigned NOT NULL default 0,
+  -- Namespace for this page
+  il_from_namespace int NOT NULL default 0,
+
+  -- Filename of target image.
+  -- This is also the page_title of the file's description page;
+  -- all such pages are in namespace 6 (NS_FILE).
+  il_to varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (il_from,il_to)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/imagelinks_tmp
+       SELECT * FROM /*_*/imagelinks;
+
+DROP TABLE /*_*/imagelinks;
+
+ALTER TABLE /*_*/imagelinks_tmp RENAME TO /*_*/imagelinks;
+
+-- Reverse index, for Special:Whatlinkshere and file description page local usage
+CREATE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+
+-- Index for Special:Whatlinkshere with namespace filter
+CREATE INDEX /*i*/il_backlinks_namespace ON /*_*/imagelinks (il_from_namespace,il_to,il_from);
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-iwlinks-fix-pk.sql b/maintenance/sqlite/archives/patch-iwlinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..91ce251
--- /dev/null
@@ -0,0 +1,24 @@
+CREATE TABLE /*_*/iwlinks_tmp (
+  -- page_id of the referring page
+  iwl_from int unsigned NOT NULL default 0,
+
+  -- Interwiki prefix code of the target
+  iwl_prefix varbinary(20) NOT NULL default '',
+
+  -- Title of the target, including namespace
+  iwl_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (iwl_from,iwl_prefix,iwl_title)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/iwlinks_tmp
+       SELECT * FROM /*_*/iwlinks;
+
+DROP TABLE /*_*/iwlinks;
+
+ALTER TABLE /*_*/iwlinks_tmp RENAME TO /*_*/iwlinks;
+
+-- Index for ApiQueryIWBacklinks
+CREATE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+
+-- Index for ApiQueryIWLinks
+CREATE INDEX /*i*/iwl_prefix_from_title ON /*_*/iwlinks (iwl_prefix, iwl_from, iwl_title);
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-langlinks-fix-pk.sql b/maintenance/sqlite/archives/patch-langlinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..da096ac
--- /dev/null
@@ -0,0 +1,21 @@
+CREATE TABLE /*_*/langlinks_tmp (
+  -- page_id of the referring page
+  ll_from int unsigned NOT NULL default 0,
+
+  -- Language code of the target
+  ll_lang varbinary(20) NOT NULL default '',
+
+  -- Title of the target, including namespace
+  ll_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (ll_from,ll_lang)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/langlinks_tmp
+       SELECT * FROM /*_*/langlinks;
+
+DROP TABLE /*_*/langlinks;
+
+ALTER TABLE /*_*/langlinks_tmp RENAME TO /*_*/langlinks;
+
+-- Index for ApiQueryLangbacklinks
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-log_search-fix-pk.sql b/maintenance/sqlite/archives/patch-log_search-fix-pk.sql
new file mode 100644 (file)
index 0000000..153e415
--- /dev/null
@@ -0,0 +1,18 @@
+CREATE TABLE /*_*/log_search_tmp (
+  -- The type of ID (rev ID, log ID, rev timestamp, username)
+  ls_field varbinary(32) NOT NULL,
+  -- The value of the ID
+  ls_value varchar(255) NOT NULL,
+  -- Key to log_id
+  ls_log_id int unsigned NOT NULL default 0,
+  PRIMARY KEY (ls_field,ls_value,ls_log_id)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/log_search_tmp
+       SELECT * FROM /*_*/log_search;
+
+DROP TABLE /*_*/log_search;
+
+ALTER TABLE /*_*/log_search_tmp RENAME TO /*_*/log_search;
+
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-log_search-rename-index.sql b/maintenance/sqlite/archives/patch-log_search-rename-index.sql
deleted file mode 100644 (file)
index 4b98a0f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-CREATE UNIQUE INDEX ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
diff --git a/maintenance/sqlite/archives/patch-module_deps-fix-pk.sql b/maintenance/sqlite/archives/patch-module_deps-fix-pk.sql
new file mode 100644 (file)
index 0000000..73bcbe2
--- /dev/null
@@ -0,0 +1,16 @@
+CREATE TABLE /*_*/module_deps_tmp (
+  -- Module name
+  md_module varbinary(255) NOT NULL,
+  -- Module context vary (includes skin and language; called "md_skin" for legacy reasons)
+  md_skin varbinary(32) NOT NULL,
+  -- JSON blob with file dependencies
+  md_deps mediumblob NOT NULL,
+  PRIMARY KEY (md_module,md_skin)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/module_deps_tmp
+       SELECT * FROM /*_*/module_deps;
+
+DROP TABLE /*_*/module_deps;
+
+ALTER TABLE /*_*/module_deps_tmp RENAME TO /*_*/module_deps;
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-objectcache-fix-pk.sql b/maintenance/sqlite/archives/patch-objectcache-fix-pk.sql
new file mode 100644 (file)
index 0000000..f2bef58
--- /dev/null
@@ -0,0 +1,14 @@
+CREATE TABLE /*_*/objectcache_tmp (
+  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+  value mediumblob,
+  exptime datetime
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/objectcache_tmp
+       SELECT * FROM /*_*/objectcache;
+
+DROP TABLE /*_*/objectcache;
+
+ALTER TABLE /*_*/objectcache_tmp RENAME TO /*_*/objectcache;
+
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-pagelinks-fix-pk.sql b/maintenance/sqlite/archives/patch-pagelinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..0e84586
--- /dev/null
@@ -0,0 +1,27 @@
+CREATE TABLE /*_*/pagelinks_tmp (
+  -- Key to the page_id of the page containing the link.
+  pl_from int unsigned NOT NULL default 0,
+  -- Namespace for this page
+  pl_from_namespace int NOT NULL default 0,
+
+  -- Key to page_namespace/page_title of the target page.
+  -- The target page may or may not exist, and due to renames
+  -- and deletions may refer to different page records as time
+  -- goes by.
+  pl_namespace int NOT NULL default 0,
+  pl_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (pl_from,pl_namespace,pl_title)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/pagelinks_tmp
+       SELECT * FROM /*_*/pagelinks;
+
+DROP TABLE /*_*/pagelinks;
+
+ALTER TABLE /*_*/pagelinks_tmp RENAME TO /*_*/pagelinks;
+
+-- Reverse index, for Special:Whatlinkshere
+CREATE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+
+-- Index for Special:Whatlinkshere with namespace filter
+CREATE INDEX /*i*/pl_backlinks_namespace ON /*_*/pagelinks (pl_from_namespace,pl_namespace,pl_title,pl_from);
diff --git a/maintenance/sqlite/archives/patch-querycache_info-fix-pk.sql b/maintenance/sqlite/archives/patch-querycache_info-fix-pk.sql
new file mode 100644 (file)
index 0000000..d9483be
--- /dev/null
@@ -0,0 +1,15 @@
+CREATE TABLE /*_*/querycache_info_tmp (
+  -- Special page name
+  -- Corresponds to a qc_type value
+  qci_type varbinary(32) NOT NULL default '' PRIMARY KEY,
+
+  -- Timestamp of last update
+  qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/querycache_info_tmp
+       SELECT * FROM /*_*/querycache_info;
+
+DROP TABLE /*_*/querycache_info;
+
+ALTER TABLE /*_*/querycache_info_tmp RENAME TO /*_*/querycache_info;
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-site_stats-fix-pk.sql b/maintenance/sqlite/archives/patch-site_stats-fix-pk.sql
new file mode 100644 (file)
index 0000000..d785e98
--- /dev/null
@@ -0,0 +1,33 @@
+CREATE TABLE /*_*/site_stats_tmp (
+  -- The single row should contain 1 here.
+  ss_row_id int unsigned NOT NULL PRIMARY KEY,
+
+  -- Total number of edits performed.
+  ss_total_edits bigint unsigned default 0,
+
+  -- An approximate count of pages matching the following criteria:
+  -- * in namespace 0
+  -- * not a redirect
+  -- * contains the text '[['
+  -- See Article::isCountable() in includes/Article.php
+  ss_good_articles bigint unsigned default 0,
+
+  -- Total pages, theoretically equal to SELECT COUNT(*) FROM page; except faster
+  ss_total_pages bigint default '-1',
+
+  -- Number of users, theoretically equal to SELECT COUNT(*) FROM user;
+  ss_users bigint default '-1',
+
+  -- Number of users that still edit
+  ss_active_users bigint default '-1',
+
+  -- Number of images, equivalent to SELECT COUNT(*) FROM image
+  ss_images int default 0
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/site_stats_tmp
+       SELECT * FROM /*_*/site_stats;
+
+DROP TABLE /*_*/site_stats;
+
+ALTER TABLE /*_*/site_stats_tmp RENAME TO /*_*/site_stats;
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-templatelinks-fix-pk.sql b/maintenance/sqlite/archives/patch-templatelinks-fix-pk.sql
new file mode 100644 (file)
index 0000000..5f09f60
--- /dev/null
@@ -0,0 +1,27 @@
+CREATE TABLE /*_*/templatelinks_tmp (
+  -- Key to the page_id of the page containing the link.
+  tl_from int unsigned NOT NULL default 0,
+  -- Namespace for this page
+  tl_from_namespace int NOT NULL default 0,
+
+  -- Key to page_namespace/page_title of the target page.
+  -- The target page may or may not exist, and due to renames
+  -- and deletions may refer to different page records as time
+  -- goes by.
+  tl_namespace int NOT NULL default 0,
+  tl_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (tl_from,tl_namespace,tl_title)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/templatelinks_tmp
+       SELECT * FROM /*_*/templatelinks;
+
+DROP TABLE /*_*/templatelinks;
+
+ALTER TABLE /*_*/templatelinks_tmp RENAME TO /*_*/templatelinks;
+
+-- Reverse index, for Special:Whatlinkshere
+CREATE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+
+-- Index for Special:Whatlinkshere with namespace filter
+CREATE INDEX /*i*/tl_backlinks_namespace ON /*_*/templatelinks (tl_from_namespace,tl_namespace,tl_title,tl_from);
diff --git a/maintenance/sqlite/archives/patch-text-fix-pk.sql b/maintenance/sqlite/archives/patch-text-fix-pk.sql
new file mode 100644 (file)
index 0000000..380887b
--- /dev/null
@@ -0,0 +1,37 @@
+CREATE TABLE /*_*/text_tmp (
+  -- Unique text storage key number.
+  -- Note that the 'oldid' parameter used in URLs does *not*
+  -- refer to this number anymore, but to rev_id.
+  --
+  -- revision.rev_text_id is a key to this column
+  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+  -- Depending on the contents of the old_flags field, the text
+  -- may be convenient plain text, or it may be funkily encoded.
+  old_text mediumblob NOT NULL,
+
+  -- Comma-separated list of flags:
+  -- gzip: text is compressed with PHP's gzdeflate() function.
+  -- utf-8: text was stored as UTF-8.
+  --        If $wgLegacyEncoding option is on, rows *without* this flag
+  --        will be converted to UTF-8 transparently at load time. Note
+  --        that due to a bug in a maintenance script, this flag may
+  --        have been stored as 'utf8' in some cases (T18841).
+  -- object: text field contained a serialized PHP object.
+  --         The object either contains multiple versions compressed
+  --         together to achieve a better compression ratio, or it refers
+  --         to another row where the text can be found.
+  -- external: text was stored in an external location specified by old_text.
+  --           Any additional flags apply to the data stored at that URL, not
+  --           the URL itself. The 'object' flag is *not* set for URLs of the
+  --           form 'DB://cluster/id/itemid', because the external storage
+  --           system itself decompresses these.
+  old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+
+INSERT INTO /*_*/text_tmp
+       SELECT * FROM /*_*/text;
+
+DROP TABLE /*_*/text;
+
+ALTER TABLE /*_*/text_tmp RENAME TO /*_*/text;
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-transcache-fix-pk.sql b/maintenance/sqlite/archives/patch-transcache-fix-pk.sql
new file mode 100644 (file)
index 0000000..53f83e1
--- /dev/null
@@ -0,0 +1,12 @@
+CREATE TABLE /*_*/transcache_tmp (
+  tc_url varbinary(255) NOT NULL PRIMARY KEY,
+  tc_contents text,
+  tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/transcache_tmp
+       SELECT * FROM /*_*/transcache;
+
+DROP TABLE /*_*/transcache;
+
+ALTER TABLE /*_*/transcache_tmp RENAME TO /*_*/transcache;
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-user_former_groups-fix-pk.sql b/maintenance/sqlite/archives/patch-user_former_groups-fix-pk.sql
new file mode 100644 (file)
index 0000000..4f5d622
--- /dev/null
@@ -0,0 +1,13 @@
+CREATE TABLE /*_*/user_former_groups_tmp (
+  -- Key to user_id
+  ufg_user int unsigned NOT NULL default 0,
+  ufg_group varbinary(255) NOT NULL default '',
+  PRIMARY KEY (ufg_user,ufg_group)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/user_former_groups_tmp
+       SELECT * FROM /*_*/user_former_groups;
+
+DROP TABLE /*_*/user_former_groups;
+
+ALTER TABLE /*_*/user_former_groups_tmp RENAME TO /*_*/user_former_groups;
\ No newline at end of file
diff --git a/maintenance/sqlite/archives/patch-user_properties-fix-pk.sql b/maintenance/sqlite/archives/patch-user_properties-fix-pk.sql
new file mode 100644 (file)
index 0000000..8362d23
--- /dev/null
@@ -0,0 +1,20 @@
+CREATE TABLE /*_*/user_properties_tmp (
+  -- Foreign key to user.user_id
+  up_user int NOT NULL,
+
+  -- Name of the option being saved. This is indexed for bulk lookup.
+  up_property varbinary(255) NOT NULL,
+
+  -- Property value as a string.
+  up_value blob,
+  PRIMARY KEY (up_user,up_property)
+) /*$wgDBTableOptions*/;
+
+INSERT INTO /*_*/user_properties_tmp
+       SELECT * FROM /*_*/user_properties;
+
+DROP TABLE /*_*/user_properties;
+
+ALTER TABLE /*_*/user_properties_tmp RENAME TO /*_*/user_properties;
+
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
\ No newline at end of file
index 1497d6f..d6ef40c 100644 (file)
@@ -178,11 +178,10 @@ CREATE INDEX /*i*/ug_expiry ON /*_*/user_groups (ug_expiry);
 CREATE TABLE /*_*/user_former_groups (
   -- Key to user_id
   ufg_user int unsigned NOT NULL default 0,
-  ufg_group varbinary(255) NOT NULL default ''
+  ufg_group varbinary(255) NOT NULL default '',
+  PRIMARY KEY (ufg_user,ufg_group)
 ) /*$wgDBTableOptions*/;
 
-CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
-
 --
 -- Stores notifications of user talk page changes, for the display
 -- of the "you have new messages" box
@@ -220,10 +219,10 @@ CREATE TABLE /*_*/user_properties (
   up_property varbinary(255) NOT NULL,
 
   -- Property value as a string.
-  up_value blob
+  up_value blob,
+  PRIMARY KEY (up_user,up_property)
 ) /*$wgDBTableOptions*/;
 
-CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
 CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
 
 --
@@ -346,10 +345,9 @@ CREATE TABLE /*_*/revision (
   -- or a rollback to a previous version.
   rev_text_id int unsigned NOT NULL,
 
-  -- Text comment summarizing the change.
-  -- This text is shown in the history and other changes lists,
-  -- rendered in a subset of wiki markup by Linker::formatComment()
-  rev_comment varbinary(767) NOT NULL,
+  -- Text comment summarizing the change. Deprecated in favor of
+  -- revision_comment_temp.revcomment_comment_id.
+  rev_comment varbinary(767) NOT NULL default '',
 
   -- Key to user.user_id of the user who made this edit.
   -- Stores 0 for anonymous edits and for some mass imports.
@@ -410,6 +408,23 @@ CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timesta
 -- and is a logged-in user.
 CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
 
+--
+-- Temporary table to avoid blocking on an alter of revision.
+--
+-- On large wikis like the English Wikipedia, altering the revision table is a
+-- months-long process. This table is being created to avoid such an alter, and
+-- will be merged back into revision in the future.
+--
+CREATE TABLE /*_*/revision_comment_temp (
+  -- Key to rev_id
+  revcomment_rev int unsigned NOT NULL,
+  -- Key to comment_id
+  revcomment_comment_id bigint unsigned NOT NULL,
+  PRIMARY KEY (revcomment_rev, revcomment_comment_id)
+) /*$wgDBTableOptions*/;
+-- Ensure uniqueness
+CREATE UNIQUE INDEX /*i*/revcomment_rev ON /*_*/revision_comment_temp (revcomment_rev);
+
 --
 -- Every time an edit by a logged out user is saved,
 -- a row is created in ip_changes. This stores
@@ -475,6 +490,40 @@ CREATE TABLE /*_*/text (
 -- In case tables are created as MyISAM, use row hints for MySQL <5.0 to avoid 4GB limit
 
 
+--
+-- Edits, blocks, and other actions typically have a textual comment describing
+-- the action. They are stored here to reduce the size of the main tables, and
+-- to allow for deduplication.
+--
+-- Deduplication is currently best-effort to avoid locking on inserts that
+-- would be required for strict deduplication. There MAY be multiple rows with
+-- the same comment_text and comment_data.
+--
+CREATE TABLE /*_*/comment (
+  -- Unique ID to identify each comment
+  comment_id bigint unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+  -- Hash of comment_text and comment_data, for deduplication
+  comment_hash INT NOT NULL,
+
+  -- Text comment summarizing the change.
+  -- This text is shown in the history and other changes lists,
+  -- rendered in a subset of wiki markup by Linker::formatComment()
+  -- Size limits are enforced at the application level, and should
+  -- take care to crop UTF-8 strings appropriately.
+  comment_text BLOB NOT NULL,
+
+  -- JSON data, intended for localizing auto-generated comments.
+  -- This holds structured data that is intended to be used to provide
+  -- localized versions of automatically-generated comments. When not empty,
+  -- comment_text should be the generated comment localized using the wiki's
+  -- content language.
+  comment_data BLOB
+) /*$wgDBTableOptions*/;
+-- Index used for deduplication.
+CREATE INDEX /*i*/comment_hash ON /*_*/comment (comment_hash);
+
+
 --
 -- Holding area for deleted articles, which may be viewed
 -- or restored by admins through the Special:Undelete interface.
@@ -496,7 +545,8 @@ CREATE TABLE /*_*/archive (
   ar_text mediumblob NOT NULL,
 
   -- Basic revision stuff...
-  ar_comment varbinary(767) NOT NULL,
+  ar_comment varbinary(767) NOT NULL default '', -- Deprecated in favor of ar_comment_id
+  ar_comment_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ar_comment should be used)
   ar_user int unsigned NOT NULL default 0,
   ar_user_text varchar(255) binary NOT NULL,
   ar_timestamp binary(14) NOT NULL default '',
@@ -576,12 +626,10 @@ CREATE TABLE /*_*/pagelinks (
   -- and deletions may refer to different page records as time
   -- goes by.
   pl_namespace int NOT NULL default 0,
-  pl_title varchar(255) binary NOT NULL default ''
+  pl_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (pl_from,pl_namespace,pl_title)
 ) /*$wgDBTableOptions*/;
 
--- Forward index, for page edit, save
-CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
-
 -- Reverse index, for Special:Whatlinkshere
 CREATE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
 
@@ -603,12 +651,10 @@ CREATE TABLE /*_*/templatelinks (
   -- and deletions may refer to different page records as time
   -- goes by.
   tl_namespace int NOT NULL default 0,
-  tl_title varchar(255) binary NOT NULL default ''
+  tl_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (tl_from,tl_namespace,tl_title)
 ) /*$wgDBTableOptions*/;
 
--- Forward index, for page edit, save
-CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
-
 -- Reverse index, for Special:Whatlinkshere
 CREATE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
 
@@ -630,12 +676,10 @@ CREATE TABLE /*_*/imagelinks (
   -- Filename of target image.
   -- This is also the page_title of the file's description page;
   -- all such pages are in namespace 6 (NS_FILE).
-  il_to varchar(255) binary NOT NULL default ''
+  il_to varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (il_from,il_to)
 ) /*$wgDBTableOptions*/;
 
--- Forward index, for cache invalidation on file update, etc.
-CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
-
 -- Reverse index, for Special:Whatlinkshere and file description page local usage
 CREATE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
 
@@ -685,10 +729,10 @@ CREATE TABLE /*_*/categorylinks (
   -- paginate the three categories separately.  This never has to be updated
   -- after the page is created, since none of these page types can be moved to
   -- any other.
-  cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+  cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page',
+  PRIMARY KEY (cl_from,cl_to)
 ) /*$wgDBTableOptions*/;
 
-CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
 
 -- We always sort within a given category, and within a given type.  FIXME:
 -- Formerly this index didn't cover cl_type (since that didn't exist), so old
@@ -787,12 +831,10 @@ CREATE TABLE /*_*/langlinks (
   ll_lang varbinary(20) NOT NULL default '',
 
   -- Title of the target, including namespace
-  ll_title varchar(255) binary NOT NULL default ''
+  ll_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (ll_from,ll_lang)
 ) /*$wgDBTableOptions*/;
 
--- Forward index, for page edit, save, ApiQueryLanglinks
-CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
-
 -- Index for ApiQueryLangbacklinks
 CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
 
@@ -808,12 +850,10 @@ CREATE TABLE /*_*/iwlinks (
   iwl_prefix varbinary(20) NOT NULL default '',
 
   -- Title of the target, including namespace
-  iwl_title varchar(255) binary NOT NULL default ''
+  iwl_title varchar(255) binary NOT NULL default '',
+  PRIMARY KEY (iwl_from,iwl_prefix,iwl_title)
 ) /*$wgDBTableOptions*/;
 
--- Forward index, for page edit, save, ApiQueryIWLinks
-CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
-
 -- Index for ApiQueryIWBacklinks
 CREATE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
 
@@ -827,7 +867,7 @@ CREATE INDEX /*i*/iwl_prefix_from_title ON /*_*/iwlinks (iwl_prefix, iwl_from, i
 --
 CREATE TABLE /*_*/site_stats (
   -- The single row should contain 1 here.
-  ss_row_id int unsigned NOT NULL,
+  ss_row_id int unsigned NOT NULL PRIMARY KEY,
 
   -- Total number of edits performed.
   ss_total_edits bigint unsigned default 0,
@@ -852,9 +892,6 @@ CREATE TABLE /*_*/site_stats (
   ss_images int default 0
 ) /*$wgDBTableOptions*/;
 
--- Pointless index to assuage developer superstitions
-CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
-
 --
 -- The internet is full of jerks, alas. Sometimes it's handy
 -- to block a vandal or troll account.
@@ -875,8 +912,12 @@ CREATE TABLE /*_*/ipblocks (
   -- User name of blocker
   ipb_by_text varchar(255) binary NOT NULL default '',
 
-  -- Text comment made by blocker.
-  ipb_reason varbinary(767) NOT NULL,
+  -- Text comment made by blocker. Deprecated in favor of ipb_reason_id
+  ipb_reason varbinary(767) NOT NULL default '',
+
+  -- Key to comment_id. Text comment made by blocker.
+  -- ("DEFAULT 0" is temporary, signaling that ipb_reason should be used)
+  ipb_reason_id bigint unsigned NOT NULL DEFAULT 0,
 
   -- Creation (or refresh) date in standard YMDHMS form.
   -- IP blocks expire automatically.
@@ -983,7 +1024,8 @@ CREATE TABLE /*_*/image (
 
   -- Description field as entered by the uploader.
   -- This is displayed in image upload history and logs.
-  img_description varbinary(767) NOT NULL,
+  -- Deprecated in favor of image_comment_temp.imgcomment_description_id.
+  img_description varbinary(767) NOT NULL default '',
 
   -- user_id and user_name of uploader.
   img_user int unsigned NOT NULL default 0,
@@ -1008,6 +1050,23 @@ CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
 -- Used to get media of one type
 CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
 
+--
+-- Temporary table to avoid blocking on an alter of image.
+--
+-- On large wikis like Wikimedia Commons, altering the image table is a
+-- months-long process. This table is being created to avoid such an alter, and
+-- will be merged back into image in the future.
+--
+CREATE TABLE /*_*/image_comment_temp (
+  -- Key to img_name (ugh)
+  imgcomment_name varchar(255) binary NOT NULL,
+  -- Key to comment_id
+  imgcomment_description_id bigint unsigned NOT NULL,
+  PRIMARY KEY (imgcomment_name, imgcomment_description_id)
+) /*$wgDBTableOptions*/;
+-- Ensure uniqueness
+CREATE UNIQUE INDEX /*i*/imgcomment_name ON /*_*/image_comment_temp (imgcomment_name);
+
 
 --
 -- Previous revisions of uploaded files.
@@ -1027,7 +1086,8 @@ CREATE TABLE /*_*/oldimage (
   oi_width int NOT NULL default 0,
   oi_height int NOT NULL default 0,
   oi_bits int NOT NULL default 0,
-  oi_description varbinary(767) NOT NULL,
+  oi_description varbinary(767) NOT NULL default '', -- Deprecated.
+  oi_description_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that oi_description should be used)
   oi_user int unsigned NOT NULL default 0,
   oi_user_text varchar(255) binary NOT NULL,
   oi_timestamp binary(14) NOT NULL default '',
@@ -1075,7 +1135,8 @@ CREATE TABLE /*_*/filearchive (
   -- Deletion information, if this file is deleted.
   fa_deleted_user int,
   fa_deleted_timestamp binary(14) default '',
-  fa_deleted_reason varbinary(767) default '',
+  fa_deleted_reason varbinary(767) default '', -- Deprecated
+  fa_deleted_reason_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that fa_deleted_reason should be used)
 
   -- Duped fields from image
   fa_size int unsigned default 0,
@@ -1086,7 +1147,8 @@ CREATE TABLE /*_*/filearchive (
   fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE", "3D") default NULL,
   fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown",
   fa_minor_mime varbinary(100) default "unknown",
-  fa_description varbinary(767),
+  fa_description varbinary(767) default '', -- Deprecated
+  fa_description_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that fa_description should be used)
   fa_user int unsigned default 0,
   fa_user_text varchar(255) binary,
   fa_timestamp binary(14) default '',
@@ -1184,7 +1246,8 @@ CREATE TABLE /*_*/recentchanges (
   rc_title varchar(255) binary NOT NULL default '',
 
   -- as in revision...
-  rc_comment varbinary(767) NOT NULL default '',
+  rc_comment varbinary(767) NOT NULL default '', -- Deprecated.
+  rc_comment_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that rc_comment should be used)
   rc_minor tinyint unsigned NOT NULL default 0,
 
   -- Edits by user accounts with the 'bot' rights key are
@@ -1381,13 +1444,11 @@ CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
 -- Cache of interwiki transclusion
 --
 CREATE TABLE /*_*/transcache (
-  tc_url varbinary(255) NOT NULL,
+  tc_url varbinary(255) NOT NULL PRIMARY KEY,
   tc_contents text,
   tc_time binary(14) NOT NULL
 ) /*$wgDBTableOptions*/;
 
-CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
-
 
 CREATE TABLE /*_*/logging (
   -- Log ID, for referring to this specific log entry, probably for deletion and such.
@@ -1415,8 +1476,13 @@ CREATE TABLE /*_*/logging (
   log_page int unsigned NULL,
 
   -- Freeform text. Interpreted as edit history comments.
+  -- Deprecated in favor of log_comment_id.
   log_comment varbinary(767) NOT NULL default '',
 
+  -- Key to comment_id. Comment summarizing the change.
+  -- ("DEFAULT 0" is temporary, signaling that log_comment should be used)
+  log_comment_id bigint unsigned NOT NULL DEFAULT 0,
+
   -- miscellaneous parameters:
   -- LF separated list (old system) or serialized PHP array (new system)
   log_params blob NOT NULL,
@@ -1460,9 +1526,9 @@ CREATE TABLE /*_*/log_search (
   -- The value of the ID
   ls_value varchar(255) NOT NULL,
   -- Key to log_id
-  ls_log_id int unsigned NOT NULL default 0
+  ls_log_id int unsigned NOT NULL default 0,
+  PRIMARY KEY (ls_field,ls_value,ls_log_id)
 ) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
 CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
 
 
@@ -1514,14 +1580,12 @@ CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
 CREATE TABLE /*_*/querycache_info (
   -- Special page name
   -- Corresponds to a qc_type value
-  qci_type varbinary(32) NOT NULL default '',
+  qci_type varbinary(32) NOT NULL default '' PRIMARY KEY,
 
   -- Timestamp of last update
   qci_timestamp binary(14) NOT NULL default '19700101000000'
 ) /*$wgDBTableOptions*/;
 
-CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
-
 
 -- For each redirect, this table contains exactly one row defining its target
 CREATE TABLE /*_*/redirect (
@@ -1592,7 +1656,8 @@ CREATE TABLE /*_*/protected_titles (
   pt_namespace int NOT NULL,
   pt_title varchar(255) binary NOT NULL,
   pt_user int unsigned NOT NULL,
-  pt_reason varbinary(767),
+  pt_reason varbinary(767) default '', -- Deprecated.
+  pt_reason_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that pt_reason should be used)
   pt_timestamp binary(14) NOT NULL,
   pt_expiry varbinary(14) NOT NULL default '',
   pt_create_perm varbinary(60) NOT NULL
@@ -1686,9 +1751,9 @@ CREATE TABLE /*_*/module_deps (
   -- Module context vary (includes skin and language; called "md_skin" for legacy reasons)
   md_skin varbinary(32) NOT NULL,
   -- JSON blob with file dependencies
-  md_deps mediumblob NOT NULL
+  md_deps mediumblob NOT NULL,
+  PRIMARY KEY (md_module,md_skin)
 ) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
 
 -- Holds all the sites known to the wiki.
 CREATE TABLE /*_*/sites (
index 459ad83..3a5de66 100644 (file)
@@ -1056,6 +1056,7 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.hlist' => [
+               'targets' => [ 'desktop', 'mobile' ],
                'styles' => [
                        'resources/src/mediawiki/mediawiki.hlist-allskins.less',
                ],
@@ -1428,7 +1429,7 @@ return [
        ],
        'mediawiki.action.edit.styles' => [
                'targets' => [ 'desktop', 'mobile' ],
-               'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.styles.css',
+               'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.styles.less',
        ],
        'mediawiki.action.edit.collapsibleFooter' => [
                'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js',
@@ -1802,6 +1803,8 @@ return [
                        'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.HighlightColorPickerWidget.js',
                        'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js',
                        'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MarkSeenButtonWidget.js',
+                       'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.RcTopSectionWidget.js',
+                       'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.WatchlistTopSectionWidget.js',
                        'resources/src/mediawiki.rcfilters/mw.rcfilters.HighlightColors.js',
                        'resources/src/mediawiki.rcfilters/mw.rcfilters.init.js',
                ],
@@ -1829,6 +1832,8 @@ return [
                        'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListItemWidget.less',
                        'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SaveFiltersPopupButtonWidget.less',
                        'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less',
+                       'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.RcTopSectionWidget.less',
+                       'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.WatchlistTopSectionWidget.less',
                ],
                'skinStyles' => [
                        'monobook' => [
@@ -1894,7 +1899,8 @@ return [
                        'rcfilters-liveupdates-button',
                        'rcfilters-liveupdates-button-title-on',
                        'rcfilters-liveupdates-button-title-off',
-                       'rcfilters-watchlist-markSeen-button',
+                       'rcfilters-watchlist-markseen-button',
+                       'rcfilters-watchlist-edit-watchlist-button',
                        'rcfilters-other-review-tools',
                        'blanknamespace',
                        'namespaces',
@@ -2186,8 +2192,9 @@ return [
                'styles' => 'resources/src/mediawiki.special/mediawiki.special.upload.styles.css',
        ],
        'mediawiki.special.userlogin.common.styles' => [
-               'styles' => [
-                       'resources/src/mediawiki.special/mediawiki.special.userlogin.common.css',
+               'targets' => [ 'desktop', 'mobile' ],
+               'skinStyles' => [
+                       'default' => 'resources/src/mediawiki.special/mediawiki.special.userlogin.common.css',
                ],
        ],
        'mediawiki.special.userlogin.login.styles' => [
diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.styles.css b/resources/src/mediawiki.action/mediawiki.action.edit.styles.css
deleted file mode 100644 (file)
index 7b6aaa8..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*!
- * Styles for elements of the editing form.
- */
-
-/* General layout */
-#wpTextbox1 {
-       margin: 0;
-       display: block;
-       /* Ensure the textarea is not higher than browser's viewport on small screens */
-       max-height: 100vh;
-       /* But don't let it collapse into nothingness on really tiny screens */
-       min-height: 5em;
-}
-
-/*
- * Add a bit of margin space between the preview and the toolbar.
- * This replaces the ugly <p><br /></p> we used to insert into the page source
- */
-#wikiPreview.ontop {
-       margin-bottom: 1em;
-}
-
-/* Adjustments to edit form elements */
-#editpage-copywarn {
-       font-size: 0.9em;
-}
-
-.mw-editform-ooui #wpSummaryWidget {
-       display: block;
-       margin-bottom: 1em;
-       max-width: none;
-}
-
-.mw-editform-ooui #editpage-copywarn {
-       line-height: 1.26;
-}
-
-.mw-editform-ooui #wpSummaryLabel {
-       margin: 0;
-}
-
-.mw-editform-ooui .editCheckboxes .oo-ui-fieldLayout {
-       margin-right: 1em;
-}
-
-.mw-editform-ooui .editHelp {
-       margin-left: 0.5em;
-       vertical-align: middle;
-}
-
-.mw-editform-ooui .editHelp a {
-       font-weight: bold;
-}
-
-.mw-editform-ooui .editOptions {
-       border-radius: 0 0 2px 2px;
-}
diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.styles.less b/resources/src/mediawiki.action/mediawiki.action.edit.styles.less
new file mode 100644 (file)
index 0000000..dc6a366
--- /dev/null
@@ -0,0 +1,56 @@
+/*!
+ * Styles for elements of the editing form.
+ */
+
+/*
+ * Add a bit of margin space between the preview and the toolbar.
+ * This replaces the ugly <p><br /></p> we used to insert into the page source
+ */
+#wikiPreview.ontop {
+       margin-bottom: 1em;
+}
+
+.mw-editform {
+       /* General layout */
+       #wpTextbox1 {
+               margin: 0;
+               display: block;
+               /* Ensure the textarea is not higher than browser's viewport on small screens */
+               max-height: 100vh;
+               /* But don't let it collapse into nothingness on really tiny screens */
+               min-height: 5em;
+       }
+
+       /* Adjustments to edit form elements */
+       #editpage-copywarn {
+               font-size: 0.9em;
+               line-height: 1.26;
+       }
+
+       #wpSummaryWidget {
+               display: block;
+               margin-bottom: 1em;
+               max-width: none;
+       }
+
+       #wpSummaryLabel {
+               margin: 0;
+       }
+
+       .editCheckboxes .oo-ui-fieldLayout {
+               margin-right: 1em;
+       }
+
+       .editHelp {
+               margin-left: 0.5em;
+               vertical-align: middle;
+
+               a {
+                       font-weight: bold;
+               }
+       }
+
+       .editOptions {
+               border-radius: 0 0 2px 2px;
+       }
+}
index a8ee06b..62ba002 100644 (file)
                return result;
        };
 
+       /**
+        * Get an array of currently applied highlight colors
+        *
+        * @return {string[]} Currently applied highlight colors
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentlyUsedHighlightColors = function () {
+               var result = [];
+
+               this.getHighlightedItems().forEach( function ( filterItem ) {
+                       var color = filterItem.getHighlightColor();
+
+                       if ( result.indexOf( color ) === -1 ) {
+                               result.push( color );
+                       }
+               } );
+
+               return result;
+       };
+
        /**
         * Sanitize value group of a string_option groups type
         * Remove duplicates and make sure to only use valid
index a7f3d23..d87ef73 100644 (file)
@@ -18,6 +18,7 @@
                OO.EmitterList.call( this );
 
                this.default = config.default;
+               this.baseState = {};
 
                // Events
                this.aggregate( { update: 'itemUpdate' } );
         * @return {mw.rcfilters.dm.SavedQueryItemModel} Matching item model
         */
        mw.rcfilters.dm.SavedQueriesModel.prototype.findMatchingQuery = function ( fullQueryComparison ) {
+               var model = this;
+
+               fullQueryComparison = this.getDifferenceFromBase( fullQueryComparison );
+
                return this.getItems().filter( function ( item ) {
+                       var comparedData = model.getDifferenceFromBase( item.getData() );
                        return OO.compare(
-                               item.getData(),
+                               comparedData,
                                fullQueryComparison
                        );
                } )[ 0 ];
        };
 
+       /**
+        * Get a minimal representation of the state for comparison
+        *
+        * @param {Object} state Given state
+        * @return {Object} Minimal state
+        */
+       mw.rcfilters.dm.SavedQueriesModel.prototype.getDifferenceFromBase = function ( state ) {
+               var result = { filters: {}, highlights: {}, invert: state.invert },
+                       baseState = this.baseState;
+
+               // XOR results
+               $.each( state.filters, function ( name, value ) {
+                       if ( baseState.filters !== undefined && baseState.filters[ name ] !== value ) {
+                               result.filters[ name ] = value;
+                       }
+               } );
+
+               $.each( state.highlights, function ( name, value ) {
+                       if ( baseState.highlights !== undefined && baseState.highlights[ name ] !== value && name !== 'highlight' ) {
+                               result.highlights[ name ] = value;
+                       }
+               } );
+
+               return result;
+       };
        /**
         * Get query by its identifier
         *
index a0e60d5..81bfb47 100644 (file)
@@ -40,7 +40,7 @@
         * @param {Object} [tagList] Tag definition
         */
        mw.rcfilters.Controller.prototype.initialize = function ( filterStructure, namespaceStructure, tagList ) {
-               var parsedSavedQueries, limitDefault,
+               var parsedSavedQueries,
                        displayConfig = mw.config.get( 'StructuredChangeFiltersDisplayConfig' ),
                        controller = this,
                        views = {},
                        };
                }
 
-               // Convert the default from the old preference
-               // since the limit preference actually affects more
-               // than just the RecentChanges page
-               limitDefault = Number( mw.user.options.get( 'rclimit', '50' ) );
-
                // Add parameter range operations
                views.range = {
                        groups: [
                                                max: 1000
                                        },
                                        sortFunc: function ( a, b ) { return Number( a.name ) - Number( b.name ); },
-                                       'default': String( limitDefault ),
+                                       'default': displayConfig.limitDefault,
                                        // Temporarily making this not sticky until we resolve the problem
                                        // with the misleading preference. Note that if this is to be permanent
                                        // we should remove all sticky behavior methods completely
                                                        ( Number( i ) * 24 ).toFixed( 2 ) :
                                                        Number( i );
                                        },
-                                       'default': mw.user.options.get( 'rcdays', '30' ),
+                                       'default': displayConfig.daysDefault,
                                        // Temporarily making this not sticky while limit is not sticky, see above
                                        // isSticky: true,
                                        excludedFromSavedQueries: true,
                                }
                        } );
 
-                       return $.extend( true, {}, savedParams, savedHighlights );
+                       return $.extend( true, {}, savedParams, savedHighlights, { invert: String( Number( data.invert || 0 ) ) } );
                }
 
                return this.filtersModel.getDefaultParams();
index a1ef981..1894b61 100644 (file)
                this.filtersModel.toggleInvertedNamespaces( !!Number( parameters.invert ) );
 
                // Update highlight state
-               this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
                this.filtersModel.getItems().forEach( function ( filterItem ) {
                        var color = parameters[ filterItem.getName() + '_color' ];
                        if ( color ) {
                                filterItem.clearHighlightColor();
                        }
                } );
+               this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
 
                // Check all filter interactions
                this.filtersModel.reassessFilterInteractions();
index a6bce14..0819351 100644 (file)
@@ -8,10 +8,10 @@
                 * @private
                 */
                init: function () {
-                       var toplinksTitle,
-                               topLinksCookieName = 'rcfilters-toplinks-collapsed-state',
-                               topLinksCookie = mw.cookie.get( topLinksCookieName ),
-                               topLinksCookieValue = topLinksCookie || 'collapsed',
+                       var $topLinks,
+                               rcTopSection,
+                               $watchlistDetails,
+                               wlTopSection,
                                savedQueriesPreferenceName = mw.config.get( 'wgStructuredChangeFiltersSavedQueriesPreferenceName' ),
                                filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
                                changesListModel = new mw.rcfilters.dm.ChangesListViewModel(),
@@ -26,7 +26,9 @@
                                        .addClass( 'mw-rcfilters-ui-overlay' ),
                                filtersWidget = new mw.rcfilters.ui.FilterWrapperWidget(
                                        controller, filtersModel, savedQueriesModel, changesListModel, { $overlay: $overlay } ),
-                               markSeenButton,
+                               savedLinksListWidget = new mw.rcfilters.ui.SavedLinksListWidget(
+                                       controller, savedQueriesModel, { $overlay: $overlay }
+                               ),
                                currentPage = mw.config.get( 'wgCanonicalNamespace' ) +
                                        ':' +
                                        mw.config.get( 'wgCanonicalSpecialPageName' );
                        controller.replaceUrl();
 
                        if ( currentPage === 'Special:Recentchanges' ) {
-                               toplinksTitle = new OO.ui.ButtonWidget( {
-                                       framed: false,
-                                       indicator: topLinksCookieValue === 'collapsed' ? 'down' : 'up',
-                                       flags: [ 'progressive' ],
-                                       label: $( '<span>' ).append( mw.message( 'rcfilters-other-review-tools' ).parse() ).contents()
-                               } );
-                               $( '.mw-recentchanges-toplinks-title' ).replaceWith( toplinksTitle.$element );
-                               // Move the top links to a designated area so it's near the
-                               // 'saved filters' button and make it collapsible
-                               $( '.mw-recentchanges-toplinks' )
-                                       .addClass( 'mw-rcfilters-ui-ready' )
-                                       .makeCollapsible( {
-                                               collapsed: topLinksCookieValue === 'collapsed',
-                                               $customTogglers: toplinksTitle.$element
-                                       } )
-                                       .on( 'beforeExpand.mw-collapsible', function () {
-                                               mw.cookie.set( topLinksCookieName, 'expanded' );
-                                               toplinksTitle.setIndicator( 'up' );
-                                       } )
-                                       .on( 'beforeCollapse.mw-collapsible', function () {
-                                               mw.cookie.set( topLinksCookieName, 'collapsed' );
-                                               toplinksTitle.setIndicator( 'down' );
-                                       } )
-                                       .appendTo( '.mw-rcfilters-ui-filterWrapperWidget-top-placeholder' );
+                               $topLinks = $( '.mw-recentchanges-toplinks' ).detach();
+
+                               rcTopSection = new mw.rcfilters.ui.RcTopSectionWidget(
+                                       savedLinksListWidget, $topLinks
+                               );
+                               filtersWidget.setTopSection( rcTopSection.$element );
                        } // end Special:RC
 
                        if ( currentPage === 'Special:Watchlist' ) {
-                               markSeenButton = new mw.rcfilters.ui.MarkSeenButtonWidget( controller, changesListModel );
-                               $( 'form#mw-watchlist-resetbutton' ).detach();
-                               filtersWidget.prependToTopRow( markSeenButton );
+                               $( '#contentSub, form#mw-watchlist-resetbutton' ).detach();
+                               $watchlistDetails = $( '.watchlistDetails' ).detach().contents();
+
+                               wlTopSection = new mw.rcfilters.ui.WatchlistTopSectionWidget(
+                                       controller, changesListModel, savedLinksListWidget, $watchlistDetails
+                               );
+                               filtersWidget.setTopSection( wlTopSection.$element );
                        } // end Special:WL
                }
        };
index c667bac..e031552 100644 (file)
@@ -7,29 +7,6 @@
                border: 0;
        }
 
-       .mw-recentchanges-toplinks {
-               padding: 0 0.5em;
-
-               .oo-ui-widget-enabled.oo-ui-buttonElement.oo-ui-buttonElement-frameless .oo-ui-buttonElement-button {
-                       padding: 0 2.5em 0 0.5em;
-               }
-
-               &-title,
-               .mw-collapsible-text {
-                       // Same as the legend
-                       font-size: 0.85em;
-               }
-
-               &:not( .mw-collapsed ) {
-                       // Same as the legend
-                       border: 1px solid #ddd;
-               }
-
-               &:not( .mw-rcfilters-ui-ready ) {
-                       display: none;
-               }
-       }
-
        .rcfilters-head {
                min-height: 200px;
 
                        .animation-delay( 0s );
                }
        }
+
+       #contentSub,
+       p.watchlistDetails,
+       form#mw-watchlist-resetbutton {
+               display: none;
+       }
 }
 
 .mw-rcfilters-staticfilters-selected {
index 27acd75..b4e3b0e 100644 (file)
        }
 }
 
+// A mixin just for changesListWrapperWidget page, to output the scope of the widget
+// so it is before the rest of the rule; we need the li& to be in
+// between the wrapper scope and the color-cX class, which doesn't
+// work if the rules are inside the above widget LESS scope
+.highlight-changesListWrapperWidget( @bgcolor ) {
+       .mw-rcfilters-ui-changesListWrapperWidget li&,
+               .mw-rcfilters-ui-changesListWrapperWidget & tr:first-child,
+               .mw-rcfilters-ui-changesListWrapperWidget tr&.mw-rcfilters-ui-changesListWrapperWidget-enhanced-toplevel:not(.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey) td:not( :nth-child( -n+2 ) ),
+               .mw-rcfilters-ui-changesListWrapperWidget tr&.mw-rcfilters-ui-changesListWrapperWidget-enhanced-nested:not(.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey) td:not( :nth-child( -n+3 ) ) {
+               background-color: @bgcolor;
+       }
+}
+
 // This mixin produces color mixes for two, three and four colors
 .highlight-color-mix( @color1, @color2, @color3: false, @color4: false ) {
        @highlight-color-class-var: ~'.mw-rcfilters-highlight-color-@{color1}.mw-rcfilters-highlight-color-@{color2}';
 
        // Two colors
        @{highlight-color-class-var} when ( @color3 = false ) and ( @color4 = false ) and not ( @color1 = false ), ( @color2 = false ) {
-               background-color: tint( average( @@c1var, @@c2var ), 50% );
+               .highlight-changesListWrapperWidget( tint( average( @@c1var, @@c2var ), 50% ) );
        }
        // Three colors
        @{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3} when ( @color4 = false ) and not ( @color3 = false ) {
                @c3var: ~'highlight-@{color3}';
-               background-color: tint( mix( @@c1var, average( @@c2var, @@c3var ), 33% ), 30% );
+               .highlight-changesListWrapperWidget( tint( mix( @@c1var, average( @@c2var, @@c3var ), 33% ), 30% ) );
        }
 
        // Four colors
        @{highlight-color-class-var}.mw-rcfilters-highlight-color-@{color3}.mw-rcfilters-highlight-color-@{color4} when not ( @color4 = false ) {
                @c3var: ~'highlight-@{color3}';
                @c4var: ~'highlight-@{color4}';
-               background-color: tint( mix( @@c1var, mix( @@c2var, average( @@c3var, @@c4var ), 25% ), 25% ), 25% );
+               .highlight-changesListWrapperWidget( tint( mix( @@c1var, mix( @@c2var, average( @@c3var, @@c4var ), 25% ), 25% ), 25% ) );
        }
 }
index 3fe9b4c..5ef60e0 100644 (file)
                }
        }
 
+       // Rule needs to be specific
+       // We want the expand button to appear outside the color
+       // to match the way the general highlight background appears
+       &-enhanced-grey td:not( :nth-child( -n+2 ) ) {
+               background-color: #dee0e3;
+       }
+
        h4:first-of-type {
                margin-top: 0;
                padding-top: 0;
@@ -77,8 +84,9 @@
                // and then plus the general margin
                width: ~'calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * 5 )';
                // And we want to shift the entire block to the left of the li
-               position: absolute;
-               left: 0;
+               position: relative;
+               // Negative left margin of width + padding
+               margin-left: ~'calc( ( @{result-circle-diameter} + @{result-circle-margin} ) * -5 - @{result-circle-general-margin} )';
 
                .mw-rcfilters-ui-changesListWrapperWidget-highlighted & {
                        display: inline-block;
                        .result-circle( c5 );
                }
        }
+}
 
-       // One color
-       .mw-rcfilters-highlight-color-c1 {
-               background-color: tint( @highlight-c1, 70% );
-       }
+// One color
+.mw-rcfilters-highlight-color-c1 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c1, 70% ); );
+}
 
-       .mw-rcfilters-highlight-color-c2 {
-               background-color: tint( @highlight-c2, 70% );
-       }
+.mw-rcfilters-highlight-color-c2 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c2, 70% ); );
+}
 
-       .mw-rcfilters-highlight-color-c3 {
-               background-color: tint( @highlight-c3, 70% );
-       }
+.mw-rcfilters-highlight-color-c3 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c3, 70% ); );
+}
 
-       .mw-rcfilters-highlight-color-c4 {
-               background-color: tint( @highlight-c4, 70% );
-       }
+.mw-rcfilters-highlight-color-c4 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c4, 70% ); );
+}
 
-       .mw-rcfilters-highlight-color-c5 {
-               background-color: tint( @highlight-c5, 70% );
-       }
+.mw-rcfilters-highlight-color-c5 {
+       .highlight-changesListWrapperWidget( tint( @highlight-c5, 70% ); );
+}
 
-       // Two colors
-       .highlight-color-mix( c1, c2 );
-       // Overriding .highlight-color-mix( c1, c3 ); to produce
-       // a custom color rather than the computed tint
-       // see https://phabricator.wikimedia.org/T161267
-       .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c3 {
-               background-color: #ccdecc;
-       }
-       .highlight-color-mix( c1, c4 );
-       .highlight-color-mix( c1, c5 );
-       .highlight-color-mix( c2, c3 );
-       .highlight-color-mix( c2, c4 );
-       .highlight-color-mix( c2, c5 );
-       .highlight-color-mix( c3, c4 );
-       .highlight-color-mix( c3, c5 );
-       .highlight-color-mix( c4, c5 );
-
-       // Three colors
-       .highlight-color-mix( c1, c2, c3 );
-       .highlight-color-mix( c1, c2, c5 );
-       .highlight-color-mix( c1, c2, c4 );
-       .highlight-color-mix( c1, c3, c4 );
-       .highlight-color-mix( c1, c3, c5 );
-       .highlight-color-mix( c1, c4, c5 );
-       .highlight-color-mix( c2, c3, c4 );
-       .highlight-color-mix( c2, c3, c5 );
-       .highlight-color-mix( c2, c4, c5 );
-       .highlight-color-mix( c3, c4, c5 );
-
-       // Four colors
-       .highlight-color-mix( c1, c2, c3, c4 );
-       .highlight-color-mix( c1, c2, c3, c5 );
-       .highlight-color-mix( c1, c2, c4, c5 );
-       .highlight-color-mix( c1, c3, c4, c5 );
-       .highlight-color-mix( c2, c3, c4, c5 );
-
-       // Five colors:
-       .mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c2.mw-rcfilters-highlight-color-c3.mw-rcfilters-highlight-color-c4.mw-rcfilters-highlight-color-c5 {
-               background-color: tint( mix( @highlight-c1, mix( @highlight-c2, mix( @highlight-c3, average( @highlight-c4, @highlight-c5 ), 20% ), 20% ), 20% ), 15% );
-       }
+// Two colors
+.highlight-color-mix( c1, c2 );
+// Overriding .highlight-color-mix( c1, c3 ); to produce
+// a custom color rather than the computed tint
+// see https://phabricator.wikimedia.org/T161267
+.mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c3 {
+       .highlight-changesListWrapperWidget( #ccdecc );
+}
+.highlight-color-mix( c1, c4 );
+.highlight-color-mix( c1, c5 );
+.highlight-color-mix( c2, c3 );
+.highlight-color-mix( c2, c4 );
+.highlight-color-mix( c2, c5 );
+.highlight-color-mix( c3, c4 );
+.highlight-color-mix( c3, c5 );
+.highlight-color-mix( c4, c5 );
+
+// Three colors
+.highlight-color-mix( c1, c2, c3 );
+.highlight-color-mix( c1, c2, c5 );
+.highlight-color-mix( c1, c2, c4 );
+.highlight-color-mix( c1, c3, c4 );
+.highlight-color-mix( c1, c3, c5 );
+.highlight-color-mix( c1, c4, c5 );
+.highlight-color-mix( c2, c3, c4 );
+.highlight-color-mix( c2, c3, c5 );
+.highlight-color-mix( c2, c4, c5 );
+.highlight-color-mix( c3, c4, c5 );
+
+// Four colors
+.highlight-color-mix( c1, c2, c3, c4 );
+.highlight-color-mix( c1, c2, c3, c5 );
+.highlight-color-mix( c1, c2, c4, c5 );
+.highlight-color-mix( c1, c3, c4, c5 );
+.highlight-color-mix( c2, c3, c4, c5 );
+
+// Five colors:
+.mw-rcfilters-highlight-color-c1.mw-rcfilters-highlight-color-c2.mw-rcfilters-highlight-color-c3.mw-rcfilters-highlight-color-c4.mw-rcfilters-highlight-color-c5 {
+       .highlight-changesListWrapperWidget( tint( mix( @highlight-c1, mix( @highlight-c2, mix( @highlight-c3, average( @highlight-c4, @highlight-c5 ), 20% ), 20% ), 20% ), 15% ) );
 }
index a89b69c..bbd2c74 100644 (file)
@@ -7,17 +7,6 @@
                margin-top: 1em;
        }
 
-       &-top {
-               &-placeholder {
-                       width: 100%;
-               }
-
-               &-savedLinks {
-                       padding-left: 1em;
-                       vertical-align: bottom;
-               }
-       }
-
        &-bottom {
                margin-top: 1em;
 
diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.RcTopSectionWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.RcTopSectionWidget.less
new file mode 100644 (file)
index 0000000..9d1cc23
--- /dev/null
@@ -0,0 +1,44 @@
+.mw-rcfilters-ui-rcTopSectionWidget {
+       &-topLinks {
+               &-table {
+                       width: 100%;
+               }
+
+               &-top {
+                       display: block;
+                       width: 100%;
+
+                       .mw-recentchanges-toplinks {
+                               margin-bottom: 0.5em;
+                       }
+               }
+       }
+
+       &-savedLinks {
+               vertical-align: bottom;
+               padding-left: 1em;
+       }
+
+       .mw-recentchanges-toplinks {
+               padding: 0 0.5em;
+
+               .oo-ui-widget-enabled.oo-ui-buttonElement.oo-ui-buttonElement-frameless .oo-ui-buttonElement-button {
+                       padding: 0 2.5em 0 0.5em;
+               }
+
+               &-title,
+               .mw-collapsible-text {
+                       // Same as the legend
+                       font-size: 0.85em;
+               }
+
+               &:not( .mw-collapsed ) {
+                       // Same as the legend
+                       border: 1px solid #ddd;
+               }
+
+               &:not( .mw-rcfilters-ui-ready ) {
+                       display: none;
+               }
+       }
+}
index e7433e2..9dccc24 100644 (file)
@@ -12,7 +12,7 @@
                vertical-align: middle;
        }
 
-       .oo-ui-iconElement-icon {
+       .mw-rcfilters-ui-savedLinksListItemWidget-icon .oo-ui-iconElement-icon {
                // Since we made the rows narrower (height smaller than usual)
                // then the icon needs to be slightly smaller as well, so that
                // when we toggle 'default' the icon doesn't bounce the option
diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.WatchlistTopSectionWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.WatchlistTopSectionWidget.less
new file mode 100644 (file)
index 0000000..5e1e118
--- /dev/null
@@ -0,0 +1,27 @@
+.mw-rcfilters-ui-watchlistTopSectionWidget {
+       &-watchlistDetails {
+               width: 100%;
+       }
+
+       &-editWatchlistButton {
+               vertical-align: bottom;
+
+               // actual button
+               .oo-ui-buttonWidget {
+                       margin-left: 3em;
+               }
+       }
+
+       &-savedLinks {
+               float: right;
+       }
+
+       .mw-rcfilters-ui-table {
+               margin-top: 1em;
+       }
+
+       &-separator {
+               margin-top: 1em;
+               border-top: 2px solid #eaecf0; // Base80 AAA
+       }
+}
index 0bee2f1..f7081af 100644 (file)
@@ -2,6 +2,10 @@
        &-table {
                display: table;
                width: 100%;
+
+               &-placeholder {
+                       width: 100%;
+               }
        }
 
        &-row {
index ec12783..f331f75 100644 (file)
                this.filtersViewModel = filtersViewModel;
                this.changesListViewModel = changesListViewModel;
                this.controller = controller;
+               this.highlightClasses = null;
+               this.filtersModelInitialized = false;
 
                // Events
                this.filtersViewModel.connect( this, {
                        itemUpdate: 'onItemUpdate',
-                       highlightChange: 'onHighlightChange'
+                       highlightChange: 'onHighlightChange',
+                       initialize: 'onFiltersModelInitialize'
                } );
                this.changesListViewModel.connect( this, {
                        invalidate: 'onModelInvalidate',
@@ -45,9 +48,6 @@
                        // We handle our own display/hide of the empty results message
                        .removeClass( 'mw-changeslist-empty' );
 
-               // Set up highlight containers
-               this.setupHighlightContainers( this.$element );
-
                this.setupNewChangesButtonContainer( this.$element );
        };
 
 
        OO.inheritClass( mw.rcfilters.ui.ChangesListWrapperWidget, OO.ui.Widget );
 
+       /**
+        * Respond to filters model initialize event
+        */
+       mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onFiltersModelInitialize = function () {
+               this.filtersModelInitialized = true;
+               // Set up highlight containers. We need to wait for the filters model
+               // to be initialized, so we can make sure we have all the css class definitions
+               // we get from the server with our filters
+               this.setupHighlightContainers( this.$element );
+       };
+
+       /**
+        * Get all available highlight classes
+        *
+        * @return {string[]} An array of available highlight class names
+        */
+       mw.rcfilters.ui.ChangesListWrapperWidget.prototype.getHighlightClasses = function () {
+               if ( !this.highlightClasses || !this.highlightClasses.length ) {
+                       this.highlightClasses = this.filtersViewModel.getItemsSupportingHighlights()
+                               .map( function ( filterItem ) {
+                                       return filterItem.getCssClass();
+                               } );
+               }
+
+               return this.highlightClasses;
+       };
+
        /**
         * Respond to the highlight feature being toggled on and off
         *
@@ -72,7 +99,7 @@
         * Respond to a filter item model update
         */
        mw.rcfilters.ui.ChangesListWrapperWidget.prototype.onItemUpdate = function () {
-               if ( this.filtersViewModel.isHighlightEnabled() ) {
+               if ( this.filtersModelInitialized && this.filtersViewModel.isHighlightEnabled() ) {
                        this.clearHighlight();
                        this.applyHighlight();
                }
         * @param {jQuery|string} $content The content of the updated changes list
         */
        mw.rcfilters.ui.ChangesListWrapperWidget.prototype.setupHighlightContainers = function ( $content ) {
-               var highlightClass = 'mw-rcfilters-ui-changesListWrapperWidget-highlights',
+               var $enhancedTopPageCell, $enhancedNestedPagesCell,
+                       widget = this,
+                       highlightClass = 'mw-rcfilters-ui-changesListWrapperWidget-highlights',
                        $highlights = $( '<div>' )
                                .addClass( highlightClass )
                                .append(
                } );
 
                if ( this.inEnhancedMode() ) {
-                       // Enhanced RC
-                       $content.find( 'td.mw-enhanced-rc' )
-                               .parent()
+                       $enhancedTopPageCell = $content.find( 'table.mw-enhanced-rc.mw-collapsible' );
+                       $enhancedNestedPagesCell = $content.find( 'td.mw-enhanced-rc-nested' );
+
+                       // Enhanced RC highlight containers
+                       $content.find( 'table.mw-enhanced-rc tr:first-child' )
+                               .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-toplevel' )
                                .prepend(
                                        $( '<td>' )
                                                .append( $highlights.clone() )
                                );
+
+                       // We are adding and changing cells in a table that, despite having nested rows,
+                       // is actually all one big table. To do that right, we want to remove the 'placeholder'
+                       // cell from the top row, because we're actually adding that placeholder in the children
+                       // with the highlights.
+                       $content.find( 'table.mw-enhanced-rc tr:first-child td.mw-changeslist-line-prefix' )
+                               .detach();
+                       $content.find( 'table.mw-enhanced-rc tr:first-child td.mw-enhanced-rc' )
+                               .prop( 'colspan', '2' );
+
+                       $enhancedNestedPagesCell
+                               .before(
+                                       $( '<td>' )
+                                               .append( $highlights.clone().addClass( 'mw-enhanced-rc-nested' ) )
+                               );
+
+                       // We need to target the nested rows differently than the top rows so that the
+                       // LESS rules applies correctly. In top rows, the rule should highlight all but
+                       // the first 2 cells td:not( :nth-child( -n+2 ) and the nested rows, the rule
+                       // should highlight all but the first 3 cells td:not( :nth-child( -n+3 )
+                       $enhancedNestedPagesCell
+                               .closest( 'tr' )
+                               .addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-nested' );
+
+                       // Go over pages that have sub results
+                       // HACK: We really only can collect those by targetting the collapsible class
+                       $enhancedTopPageCell.each( function () {
+                               var collectedClasses,
+                                       $table = $( this );
+
+                               // Go over <tr>s and pick up all recognized classes
+                               collectedClasses = widget.getHighlightClasses().filter( function ( className ) {
+                                       return $table.find( 'tr' ).hasClass( className );
+                               } );
+
+                               $table.find( 'tr:first-child' )
+                                       .addClass( collectedClasses.join( ' ' ) );
+                       } );
+
+                       $content.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhancedView' );
                } else {
                        // Regular RC
                        $content.find( 'ul.special li' )
                }
        };
 
+       /**
+        * In enhanced mode, we need to check whether the grouped results all have the
+        * same active highlights in order to see whether the "parent" of the group should
+        * be grey or highlighted normally.
+        *
+        * This is called every time highlights are applied.
+        */
+       mw.rcfilters.ui.ChangesListWrapperWidget.prototype.updateEnhancedParentHighlight = function () {
+               var activeHighlightClasses,
+                       $enhancedTopPageCell = this.$element.find( 'table.mw-enhanced-rc.mw-collapsible' );
+
+               activeHighlightClasses = this.filtersViewModel.getCurrentlyUsedHighlightColors().map( function ( color ) {
+                       return 'mw-rcfilters-highlight-color-' + color;
+               } );
+
+               // Go over top pages and their children, and figure out if all sub-pages have the
+               // same highlights between themselves. If they do, the parent should be highlighted
+               // with all colors. If classes are different, the parent should receive a grey
+               // background
+               $enhancedTopPageCell.each( function () {
+                       var firstChildClasses, $rowsWithDifferentHighlights,
+                               $table = $( this );
+
+                       // Collect the relevant classes from the first nested child
+                       firstChildClasses = activeHighlightClasses.filter( function ( className ) {
+                               return $table.find( 'tr:nth-child(2)' ).hasClass( className );
+                       } );
+                       // Filter the non-head rows and see if they all have the same classes
+                       // to the first row
+                       $rowsWithDifferentHighlights = $table.find( 'tr:not(:first-child)' ).filter( function () {
+                               var classesInThisRow,
+                                       $this = $( this );
+
+                               classesInThisRow = activeHighlightClasses.filter( function ( className ) {
+                                       return $this.hasClass( className );
+                               } );
+
+                               return !OO.compare( firstChildClasses, classesInThisRow );
+                       } );
+
+                       // If classes are different, tag the row for using grey color
+                       $table.find( 'tr:first-child' )
+                               .toggleClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey', $rowsWithDifferentHighlights.length > 0 );
+               } );
+       };
+
        /**
         * @return {boolean} Whether the changes are grouped by page
         */
                        }
                } );
 
+               if ( this.inEnhancedMode() ) {
+                       this.updateEnhancedParentHighlight();
+               }
+
                // Turn on highlights
                this.$element.addClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
        };
                this.$element.find( '[data-highlightedFilters]' )
                        .removeAttr( 'title' )
                        .removeAttr( 'data-highlightedFilters' );
+
+               // Remove grey from enhanced rows
+               this.$element.find( '.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey' )
+                       .removeClass( 'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey' );
+
                // Turn off highlights
                this.$element.removeClass( 'mw-rcfilters-ui-changesListWrapperWidget-highlighted' );
        };
index b57f5d7..db7acaa 100644 (file)
@@ -17,7 +17,7 @@
        mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget(
                controller, model, savedQueriesModel, changesListModel, config
        ) {
-               var $top, $bottom;
+               var $bottom;
                config = config || {};
 
                // Parent
                );
 
                // Initialize
-               this.$topRow = $( '<div>' )
-                       .addClass( 'mw-rcfilters-ui-row' )
-                       .append(
-                               $( '<div>' )
-                                       .addClass( 'mw-rcfilters-ui-cell' )
-                                       .addClass( 'mw-rcfilters-ui-filterWrapperWidget-top-placeholder' )
-                       );
-               $top = $( '<div>' )
-                       .addClass( 'mw-rcfilters-ui-filterWrapperWidget-top' )
-                       .addClass( 'mw-rcfilters-ui-table' )
-                       .append( this.$topRow );
+               this.$top = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-filterWrapperWidget-top' );
 
                $bottom = $( '<div>' )
                        .addClass( 'mw-rcfilters-ui-filterWrapperWidget-bottom' )
                                this.dateWidget.$element
                        );
 
-               if ( !mw.user.isAnon() ) {
-                       this.savedLinksListWidget = new mw.rcfilters.ui.SavedLinksListWidget(
-                               this.controller,
-                               this.queriesModel,
-                               { $overlay: this.$overlay }
-                       );
-
-                       this.$topRow.append(
-                               $( '<div>' )
-                                       .addClass( 'mw-rcfilters-ui-cell' )
-                                       .addClass( 'mw-rcfilters-ui-filterWrapperWidget-top-savedLinks' )
-                                       .append( this.savedLinksListWidget.$element )
-                       );
-               }
-
                if ( mw.rcfilters.featureFlags.liveUpdate ) {
                        $bottom.append( this.liveUpdateButton.$element );
                }
                this.$element
                        .addClass( 'mw-rcfilters-ui-filterWrapperWidget' )
                        .append(
-                               $top,
+                               this.$top,
                                this.filterTagWidget.$element,
                                $bottom
                        );
        /* Methods */
 
        /**
-        * Add a widget at the beginning of the top row
+        * Set the content of the top section
         *
-        * @param {OO.ui.Widget} widget Any widget
+        * @param {jQuery} $topSectionElement
         */
-       mw.rcfilters.ui.FilterWrapperWidget.prototype.prependToTopRow = function ( widget ) {
-               this.$topRow.prepend(
-                       widget.$element
-                               .addClass( 'mw-rcfilters-ui-cell' )
-               );
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.setTopSection = function ( $topSectionElement ) {
+               this.$top.append( $topSectionElement );
        };
-
 }( mediaWiki ) );
index ad99e22..cfcdf35 100644 (file)
                        this.$element.find( 'hr' ).detach();
                }
 
+               // Get rid of all <br>s, which are inside rcshowhide
+               // If we still have content in rcshowhide, the <br>s are
+               // gone. Instead, the CSS now has a rule to mark all <span>s
+               // inside .rcshowhide with display:block; to simulate newlines
+               // where they're actually needed.
+               this.$element.find( 'br' ).detach();
                if ( !this.$element.find( '.rcshowhide' ).contents().length ) {
                        this.$element.find( '.rcshowhide' ).detach();
-                       // If we're hiding rcshowhide, the '<br>'s are around it,
-                       // there's no need for them either.
-                       this.$element.find( 'br' ).detach();
                }
 
                if ( this.$element.find( '.cloption' ).text().trim() === '' ) {
                        '.rclistfrom, .rcnotefrom, .rcoptions-listfromreset'
                ).detach();
 
-               if ( this.$element.text().trim() === this.$element.find( 'legend' ).text() ) {
+               // Get rid of the legend
+               this.$element.find( 'legend' ).detach();
+
+               // Check if the element is essentially empty, and detach it if it is
+               if ( !this.$element.text().trim().length ) {
                        this.$element.detach();
                }
        };
index 073cd1e..56fe5b9 100644 (file)
@@ -14,7 +14,7 @@
 
                // Parent
                mw.rcfilters.ui.MarkSeenButtonWidget.parent.call( this, $.extend( {
-                       label: mw.message( 'rcfilters-watchlist-markSeen-button' ).text(),
+                       label: mw.message( 'rcfilters-watchlist-markseen-button' ).text(),
                        icon: 'doubleCheck'
                }, config ) );
 
diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.RcTopSectionWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.RcTopSectionWidget.js
new file mode 100644 (file)
index 0000000..f0e1241
--- /dev/null
@@ -0,0 +1,110 @@
+( function ( mw ) {
+       /**
+        * Top section (between page title and filters) on Special:Recentchanges
+        *
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {mw.rcfilters.ui.SavedLinksListWidget} savedLinksListWidget
+        * @param {jQuery} $topLinks Content of the community-defined links
+        * @param {Object} [config] Configuration object
+        */
+       mw.rcfilters.ui.RcTopSectionWidget = function MwRcfiltersUiRcTopSectionWidget(
+               savedLinksListWidget, $topLinks, config
+       ) {
+               var toplinksTitle,
+                       topLinksCookieName = 'rcfilters-toplinks-collapsed-state',
+                       topLinksCookie = mw.cookie.get( topLinksCookieName ),
+                       topLinksCookieValue = topLinksCookie || 'collapsed',
+                       widget = this;
+
+               config = config || {};
+
+               // Parent
+               mw.rcfilters.ui.RcTopSectionWidget.parent.call( this, config );
+
+               this.$topLinks = $topLinks;
+
+               toplinksTitle = new OO.ui.ButtonWidget( {
+                       framed: false,
+                       indicator: topLinksCookieValue === 'collapsed' ? 'down' : 'up',
+                       flags: [ 'progressive' ],
+                       label: $( '<span>' ).append( mw.message( 'rcfilters-other-review-tools' ).parse() ).contents()
+               } );
+
+               this.$topLinks
+                       .addClass( 'mw-rcfilters-ui-ready' )
+                       .makeCollapsible( {
+                               collapsed: topLinksCookieValue === 'collapsed',
+                               $customTogglers: toplinksTitle.$element
+                       } )
+                       .on( 'beforeExpand.mw-collapsible', function () {
+                               mw.cookie.set( topLinksCookieName, 'expanded' );
+                               toplinksTitle.setIndicator( 'up' );
+                               widget.switchTopLinks( 'expanded' );
+                       } )
+                       .on( 'beforeCollapse.mw-collapsible', function () {
+                               mw.cookie.set( topLinksCookieName, 'collapsed' );
+                               toplinksTitle.setIndicator( 'down' );
+                               widget.switchTopLinks( 'collapsed' );
+                       } );
+
+               this.$topLinks.find( '.mw-recentchanges-toplinks-title' ).replaceWith( toplinksTitle.$element );
+
+               // Create two positions for the toplinks to toggle between
+               // in the table (first cell) or up above it
+               this.$top = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-topLinks-top' );
+               this.$tableTopLinks = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-cell' )
+                       .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-topLinks-table' );
+
+               // Initialize
+               this.$element
+                       .addClass( 'mw-rcfilters-ui-rcTopSectionWidget' )
+                       .append(
+                               this.$top,
+                               $( '<div>' )
+                                       .addClass( 'mw-rcfilters-ui-table' )
+                                       .append(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-rcfilters-ui-row' )
+                                                       .append(
+                                                               this.$tableTopLinks,
+                                                               $( '<div>' )
+                                                                       .addClass( 'mw-rcfilters-ui-table-placeholder' )
+                                                                       .addClass( 'mw-rcfilters-ui-cell' ),
+                                                               !mw.user.isAnon() ?
+                                                                       $( '<div>' )
+                                                                               .addClass( 'mw-rcfilters-ui-cell' )
+                                                                               .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-savedLinks' )
+                                                                               .append( savedLinksListWidget.$element ) :
+                                                                       null
+                                                       )
+                                       )
+                       );
+
+               // Initialize top links position
+               widget.switchTopLinks( topLinksCookieValue );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.rcfilters.ui.RcTopSectionWidget, OO.ui.Widget );
+
+       /**
+        * Switch the top links widget from inside the table (when collapsed)
+        * to the 'top' (when open)
+        *
+        * @param {string} [state] The state of the top links widget: 'expanded' or 'collapsed'
+        */
+       mw.rcfilters.ui.RcTopSectionWidget.prototype.switchTopLinks = function ( state ) {
+               state = state || 'expanded';
+
+               if ( state === 'expanded' ) {
+                       this.$top.append( this.$topLinks );
+               } else {
+                       this.$tableTopLinks.append( this.$topLinks );
+               }
+       };
+}( mediaWiki ) );
diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.WatchlistTopSectionWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.WatchlistTopSectionWidget.js
new file mode 100644 (file)
index 0000000..7d78565
--- /dev/null
@@ -0,0 +1,82 @@
+( function ( mw ) {
+       /**
+        * Top section (between page title and filters) on Special:Watchlist
+        *
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {mw.rcfilters.Controller} controller
+        * @param {mw.rcfilters.dm.ChangesListViewModel} changesListModel
+        * @param {mw.rcfilters.ui.SavedLinksListWidget} savedLinksListWidget
+        * @param {jQuery} $watchlistDetails Content of the 'details' section that includes watched pages count
+        * @param {Object} [config] Configuration object
+        */
+       mw.rcfilters.ui.WatchlistTopSectionWidget = function MwRcfiltersUiWatchlistTopSectionWidget(
+               controller, changesListModel, savedLinksListWidget, $watchlistDetails, config
+       ) {
+               var editWatchlistButton,
+                       markSeenButton,
+                       $topTable,
+                       $bottomTable,
+                       $separator;
+               config = config || {};
+
+               // Parent
+               mw.rcfilters.ui.WatchlistTopSectionWidget.parent.call( this, config );
+
+               editWatchlistButton = new OO.ui.ButtonWidget( {
+                       label: mw.msg( 'rcfilters-watchlist-edit-watchlist-button' ),
+                       icon: 'edit',
+                       href: mw.config.get( 'wgStructuredChangeFiltersEditWatchlistUrl' )
+               } );
+               markSeenButton = new mw.rcfilters.ui.MarkSeenButtonWidget( controller, changesListModel );
+
+               $topTable = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-table' )
+                       .append(
+                               $( '<div>' )
+                                       .addClass( 'mw-rcfilters-ui-row' )
+                                       .append(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-rcfilters-ui-cell' )
+                                                       .addClass( 'mw-rcfilters-ui-watchlistTopSectionWidget-watchlistDetails' )
+                                                       .append( $watchlistDetails )
+                                       )
+                                       .append(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-rcfilters-ui-cell' )
+                                                       .addClass( 'mw-rcfilters-ui-watchlistTopSectionWidget-editWatchlistButton' )
+                                                       .append( editWatchlistButton.$element )
+                                       )
+                       );
+
+               $bottomTable = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-table' )
+                       .append(
+                               $( '<div>' )
+                                       .addClass( 'mw-rcfilters-ui-row' )
+                                       .append(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-rcfilters-ui-cell' )
+                                                       .append( markSeenButton.$element )
+                                       )
+                                       .append(
+                                               $( '<div>' )
+                                                       .addClass( 'mw-rcfilters-ui-cell' )
+                                                       .addClass( 'mw-rcfilters-ui-watchlistTopSectionWidget-savedLinks' )
+                                                       .append( savedLinksListWidget.$element )
+                                       )
+                       );
+
+               $separator = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-watchlistTopSectionWidget-separator' );
+
+               this.$element
+                       .addClass( 'mw-rcfilters-ui-watchlistTopSectionWidget' )
+                       .append( $topTable, $separator, $bottomTable );
+       };
+
+       /* Initialization */
+
+       OO.inheritClass( mw.rcfilters.ui.WatchlistTopSectionWidget, OO.ui.Widget );
+}( mediaWiki ) );
index 59464bd..8ec2735 100644 (file)
@@ -47,7 +47,6 @@
        }
        .iw-result__footer a {
                vertical-align: middle;
-               color: @colorGray7;
                font-style: italic;
        }
 
index 47ecfe4..640bbff 100644 (file)
@@ -1,7 +1,7 @@
 
 button_italic.png
 -------------------
-Source : http://commons.wikimedia.org/wiki/Image:Button_S_italic.png
+Source : https://commons.wikimedia.org/wiki/Image:Button_S_italic.png
 License: Public domain
-Author : Purodha Blissenbach, http://ksh.wikipedia.org/wiki/User:Purodha
+Author : Purodha Blissenbach, https://ksh.wikipedia.org/wiki/User:Purodha
 
index 97c3c05..0dab130 100644 (file)
@@ -182,13 +182,18 @@ class ParserTestRunner {
                        if ( !file_exists( $dir ) ) {
                                continue;
                        }
+                       $counter = 1;
                        $dirIterator = new RecursiveIteratorIterator(
                                new RecursiveDirectoryIterator( $dir )
                        );
                        foreach ( $dirIterator as $fileInfo ) {
                                /** @var SplFileInfo $fileInfo */
                                if ( substr( $fileInfo->getFilename(), -4 ) === '.txt' ) {
-                                       $files[] = $fileInfo->getPathname();
+                                       $name = $info['name'] . $counter;
+                                       while ( isset( $files[$name] ) ) {
+                                               $name = $info['name'] . '_' . $counter++;
+                                       }
+                                       $files[$name] = $fileInfo->getPathname();
                                }
                        }
                }
index 2fa3cb0..82d195a 100644 (file)
@@ -15807,26 +15807,6 @@ parsoid=wt2html
 <link rel="mw:PageProp/Category" href="./Category:Baz" data-parsoid='{"stx":"simple","a":{"href":"./Category:Baz"},"sa":{"href":"Category:Baz"}}'/>
 !! end
 
-!! test
-9. Categories and newlines: should behave properly with linkprefix (T87753)
-!! options
-language=ar
-!! wikitext
-foo bar
-foo bar
-[[تصنيف:Foo]]
-[[تصنيف:Bar]]
-!! html/php
-<p>foo bar
-foo bar
-</p>
-!! html/parsoid
-<p>foo bar
-foo bar</p>
-<link rel="mw:PageProp/Category" href="./تصنيف:Foo"/>
-<link rel="mw:PageProp/Category" href="./تصنيف:Bar"/>
-!! end
-
 !! test
 Category links with multiple namespaces
 !! wikitext
@@ -15874,20 +15854,6 @@ x[[Category:Foo]]y
 <p>x<link rel="mw:PageProp/Category" href="./Flokkur:Foo" data-parsoid=""/>y</p>
 !! end
 
-!! test
-Link prefix/suffixes aren't applied to language links
-!! options
-parsoid=wt2html
-language=is
-!! wikitext
-x[[es:Foo]]y
-!! html/php
-<p>xy
-</p>
-!! html/parsoid
-<p>x<link rel="mw:PageProp/Language" href="http://es.wikipedia.org/wiki/Foo" data-parsoid=""/>y</p>
-!! end
-
 !! test
 Parsoid: Serialize link to file page with colon escape
 !! options
@@ -28458,10 +28424,10 @@ wgFragmentMode=[ 'html5', 'legacy' ]
 
 <h2><span class="mw-headline" id="Foo_bar">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <h2><span class="mw-headline" id="foo_Bar_2">foo Bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id=".D0.A2.D0.B5.D1.81.D1.82"></div><h2><span class="mw-headline" id="Тест">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id=".D0.A2.D0.B5.D1.81.D1.82_2"></div><h2><span class="mw-headline" id="Тест_2">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id=".D1.82.D0.B5.D1.81.D1.82"></div><h2><span class="mw-headline" id="тест">тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id="Hey_.3C_.23_.22_.3E_.25_:_.27"></div><h2><span class="mw-headline" id="Hey_&lt;_#_&quot;_&gt;_%_:_'">Hey &lt; # " &gt;&#160;%&#160;: '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Hey &lt; # &quot; &gt; % : '">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id=".D0.A2.D0.B5.D1.81.D1.82"></span><span class="mw-headline" id="Тест">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id=".D0.A2.D0.B5.D1.81.D1.82_2"></span><span class="mw-headline" id="Тест_2">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id=".D1.82.D0.B5.D1.81.D1.82"></span><span class="mw-headline" id="тест">тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id="Hey_.3C_.23_.22_.3E_.25_:_.27"></span><span class="mw-headline" id="Hey_&lt;_#_&quot;_&gt;_%_:_'">Hey &lt; # " &gt;&#160;%&#160;: '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Hey &lt; # &quot; &gt; % : '">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <p><a href="#Foo_bar">#Foo bar</a> <a href="#foo_Bar">#foo Bar</a> <a href="#%D0%A2%D0%B5%D1%81%D1%82">#Тест</a> <a href="#%D1%82%D0%B5%D1%81%D1%82">#тест</a> <a href="#Hey_%3C_%23_%22_%3E_%25_:_%27">#Hey &lt; # " &gt;&#160;%&#160;: '</a>
 </p><p>%F0%9F%92%A9 <span id="%F0%9F%92%A9"></span>
 </p><p><a href="#%E5%95%A4%E9%85%92">#啤酒</a> <a href="#%E5%95%A4%E9%85%92">#啤酒</a>
@@ -28505,10 +28471,10 @@ wgFragmentMode=[ 'legacy', 'html5' ]
 
 <h2><span class="mw-headline" id="Foo_bar">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=1" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <h2><span class="mw-headline" id="foo_Bar_2">foo Bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=2" title="Edit section: foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id="Тест"></div><h2><span class="mw-headline" id=".D0.A2.D0.B5.D1.81.D1.82">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id="Тест_2"></div><h2><span class="mw-headline" id=".D0.A2.D0.B5.D1.81.D1.82_2">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id="тест"></div><h2><span class="mw-headline" id=".D1.82.D0.B5.D1.81.D1.82">тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<div id="Hey_&lt;_#_&quot;_&gt;_%_:_'"></div><h2><span class="mw-headline" id="Hey_.3C_.23_.22_.3E_.25_:_.27">Hey &lt; # " &gt;&#160;%&#160;: '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Hey &lt; # &quot; &gt; % : '">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id="Тест"></span><span class="mw-headline" id=".D0.A2.D0.B5.D1.81.D1.82">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=3" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id="Тест_2"></span><span class="mw-headline" id=".D0.A2.D0.B5.D1.81.D1.82_2">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id="тест"></span><span class="mw-headline" id=".D1.82.D0.B5.D1.81.D1.82">тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<h2><span id="Hey_&lt;_#_&quot;_&gt;_%_:_'"></span><span class="mw-headline" id="Hey_.3C_.23_.22_.3E_.25_:_.27">Hey &lt; # " &gt;&#160;%&#160;: '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Hey &lt; # &quot; &gt; % : '">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <p><a href="#Foo_bar">#Foo bar</a> <a href="#foo_Bar">#foo Bar</a> <a href="#.D0.A2.D0.B5.D1.81.D1.82">#Тест</a> <a href="#.D1.82.D0.B5.D1.81.D1.82">#тест</a> <a href="#Hey_.3C_.23_.22_.3E_.25_:_.27">#Hey &lt; # " &gt;&#160;%&#160;: '</a>
 </p><p>.F0.9F.92.A9 <span id=".F0.9F.92.A9"></span>
 </p><p><a href="#.E5.95.A4.E9.85.92">#啤酒</a> <a href="#.E5.95.A4.E9.85.92">#啤酒</a>
index ed1f1ae..c844e13 100644 (file)
@@ -1303,13 +1303,17 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
        private function resetDB( $db, $tablesUsed ) {
                if ( $db ) {
                        $userTables = [ 'user', 'user_groups', 'user_properties' ];
-                       $coreDBDataTables = array_merge( $userTables, [ 'page', 'revision' ] );
+                       $pageTables = [ 'page', 'revision', 'revision_comment_temp', 'comment' ];
+                       $coreDBDataTables = array_merge( $userTables, $pageTables );
 
-                       // If any of the user tables were marked as used, we should clear all of them.
+                       // If any of the user or page tables were marked as used, we should clear all of them.
                        if ( array_intersect( $tablesUsed, $userTables ) ) {
                                $tablesUsed = array_unique( array_merge( $tablesUsed, $userTables ) );
                                TestUserRegistry::clear();
                        }
+                       if ( array_intersect( $tablesUsed, $pageTables ) ) {
+                               $tablesUsed = array_unique( array_merge( $tablesUsed, $pageTables ) );
+                       }
 
                        $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
                        foreach ( $tablesUsed as $tbl ) {
diff --git a/tests/phpunit/includes/CommentStoreTest.php b/tests/phpunit/includes/CommentStoreTest.php
new file mode 100644 (file)
index 0000000..b65136a
--- /dev/null
@@ -0,0 +1,623 @@
+<?php
+
+use Wikimedia\ScopedCallback;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Database
+ * @covers CommentStore
+ * @covers CommentStoreComment
+ */
+class CommentStoreTest extends MediaWikiLangTestCase {
+
+       protected $tablesUsed = [
+               'revision',
+               'revision_comment_temp',
+               'ipblocks',
+               'comment',
+       ];
+
+       /**
+        * Create a store for a particular stage
+        * @param int $stage
+        * @param string $key
+        * @return CommentStore
+        */
+       protected function makeStore( $stage, $key ) {
+               $store = new CommentStore( $key );
+               TestingAccessWrapper::newFromObject( $store )->stage = $stage;
+               return $store;
+       }
+
+       /**
+        * @dataProvider provideGetFields
+        * @param int $stage
+        * @param string $key
+        * @param array $expect
+        */
+       public function testGetFields( $stage, $key, $expect ) {
+               $store = $this->makeStore( $stage, $key );
+               $result = $store->getFields();
+               $this->assertEquals( $expect, $result );
+       }
+
+       public static function provideGetFields() {
+               return [
+                       'Simple table, old' => [
+                               MIGRATION_OLD, 'ipb_reason',
+                               [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ],
+                       ],
+                       'Simple table, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'ipb_reason',
+                               [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
+                       ],
+                       'Simple table, write-new' => [
+                               MIGRATION_WRITE_NEW, 'ipb_reason',
+                               [ 'ipb_reason_old' => 'ipb_reason', 'ipb_reason_id' => 'ipb_reason_id' ],
+                       ],
+                       'Simple table, new' => [
+                               MIGRATION_NEW, 'ipb_reason',
+                               [ 'ipb_reason_id' => 'ipb_reason_id' ],
+                       ],
+
+                       'Revision, old' => [
+                               MIGRATION_OLD, 'rev_comment',
+                               [
+                                       'rev_comment_text' => 'rev_comment',
+                                       'rev_comment_data' => 'NULL',
+                                       'rev_comment_cid' => 'NULL',
+                               ],
+                       ],
+                       'Revision, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rev_comment',
+                               [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
+                       ],
+                       'Revision, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rev_comment',
+                               [ 'rev_comment_old' => 'rev_comment', 'rev_comment_pk' => 'rev_id' ],
+                       ],
+                       'Revision, new' => [
+                               MIGRATION_NEW, 'rev_comment',
+                               [ 'rev_comment_pk' => 'rev_id' ],
+                       ],
+
+                       'Image, old' => [
+                               MIGRATION_OLD, 'img_description',
+                               [
+                                       'img_description_text' => 'img_description',
+                                       'img_description_data' => 'NULL',
+                                       'img_description_cid' => 'NULL',
+                               ],
+                       ],
+                       'Image, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'img_description',
+                               [ 'img_description_old' => 'img_description', 'img_description_pk' => 'img_name' ],
+                       ],
+                       'Image, write-new' => [
+                               MIGRATION_WRITE_NEW, 'img_description',
+                               [ 'img_description_old' => 'img_description', 'img_description_pk' => 'img_name' ],
+                       ],
+                       'Image, new' => [
+                               MIGRATION_NEW, 'img_description',
+                               [ 'img_description_pk' => 'img_name' ],
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetJoin
+        * @param int $stage
+        * @param string $key
+        * @param array $expect
+        */
+       public function testGetJoin( $stage, $key, $expect ) {
+               $store = $this->makeStore( $stage, $key );
+               $result = $store->getJoin();
+               $this->assertEquals( $expect, $result );
+       }
+
+       public static function provideGetJoin() {
+               return [
+                       'Simple table, old' => [
+                               MIGRATION_OLD, 'ipb_reason', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'ipb_reason',
+                                               'ipb_reason_data' => 'NULL',
+                                               'ipb_reason_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Simple table, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'ipb_reason', [
+                                       'tables' => [ 'comment_ipb_reason' => 'comment' ],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
+                                               'ipb_reason_data' => 'comment_ipb_reason.comment_data',
+                                               'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Simple table, write-new' => [
+                               MIGRATION_WRITE_NEW, 'ipb_reason', [
+                                       'tables' => [ 'comment_ipb_reason' => 'comment' ],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'COALESCE( comment_ipb_reason.comment_text, ipb_reason )',
+                                               'ipb_reason_data' => 'comment_ipb_reason.comment_data',
+                                               'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'comment_ipb_reason' => [ 'LEFT JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Simple table, new' => [
+                               MIGRATION_NEW, 'ipb_reason', [
+                                       'tables' => [ 'comment_ipb_reason' => 'comment' ],
+                                       'fields' => [
+                                               'ipb_reason_text' => 'comment_ipb_reason.comment_text',
+                                               'ipb_reason_data' => 'comment_ipb_reason.comment_data',
+                                               'ipb_reason_cid' => 'comment_ipb_reason.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ],
+                                       ],
+                               ],
+                       ],
+
+                       'Revision, old' => [
+                               MIGRATION_OLD, 'rev_comment', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'rev_comment_text' => 'rev_comment',
+                                               'rev_comment_data' => 'NULL',
+                                               'rev_comment_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Revision, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rev_comment', [
+                                       'tables' => [
+                                               'temp_rev_comment' => 'revision_comment_temp',
+                                               'comment_rev_comment' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
+                                               'rev_comment_data' => 'comment_rev_comment.comment_data',
+                                               'rev_comment_cid' => 'comment_rev_comment.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                               'comment_rev_comment' => [ 'LEFT JOIN',
+                                                       'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Revision, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rev_comment', [
+                                       'tables' => [
+                                               'temp_rev_comment' => 'revision_comment_temp',
+                                               'comment_rev_comment' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'rev_comment_text' => 'COALESCE( comment_rev_comment.comment_text, rev_comment )',
+                                               'rev_comment_data' => 'comment_rev_comment.comment_data',
+                                               'rev_comment_cid' => 'comment_rev_comment.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_comment' => [ 'LEFT JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                               'comment_rev_comment' => [ 'LEFT JOIN',
+                                                       'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Revision, new' => [
+                               MIGRATION_NEW, 'rev_comment', [
+                                       'tables' => [
+                                               'temp_rev_comment' => 'revision_comment_temp',
+                                               'comment_rev_comment' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'rev_comment_text' => 'comment_rev_comment.comment_text',
+                                               'rev_comment_data' => 'comment_rev_comment.comment_data',
+                                               'rev_comment_cid' => 'comment_rev_comment.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ],
+                                               'comment_rev_comment' => [ 'JOIN',
+                                                       'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ],
+                                       ],
+                               ],
+                       ],
+
+                       'Image, old' => [
+                               MIGRATION_OLD, 'img_description', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'img_description_text' => 'img_description',
+                                               'img_description_data' => 'NULL',
+                                               'img_description_cid' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Image, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'img_description', [
+                                       'tables' => [
+                                               'temp_img_description' => 'image_comment_temp',
+                                               'comment_img_description' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
+                                               'img_description_data' => 'comment_img_description.comment_data',
+                                               'img_description_cid' => 'comment_img_description.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_img_description' => [ 'LEFT JOIN', 'temp_img_description.imgcomment_name = img_name' ],
+                                               'comment_img_description' => [ 'LEFT JOIN',
+                                                       'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Image, write-new' => [
+                               MIGRATION_WRITE_NEW, 'img_description', [
+                                       'tables' => [
+                                               'temp_img_description' => 'image_comment_temp',
+                                               'comment_img_description' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'img_description_text' => 'COALESCE( comment_img_description.comment_text, img_description )',
+                                               'img_description_data' => 'comment_img_description.comment_data',
+                                               'img_description_cid' => 'comment_img_description.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_img_description' => [ 'LEFT JOIN', 'temp_img_description.imgcomment_name = img_name' ],
+                                               'comment_img_description' => [ 'LEFT JOIN',
+                                                       'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Image, new' => [
+                               MIGRATION_NEW, 'img_description', [
+                                       'tables' => [
+                                               'temp_img_description' => 'image_comment_temp',
+                                               'comment_img_description' => 'comment',
+                                       ],
+                                       'fields' => [
+                                               'img_description_text' => 'comment_img_description.comment_text',
+                                               'img_description_data' => 'comment_img_description.comment_data',
+                                               'img_description_cid' => 'comment_img_description.comment_id',
+                                       ],
+                                       'joins' => [
+                                               'temp_img_description' => [ 'JOIN', 'temp_img_description.imgcomment_name = img_name' ],
+                                               'comment_img_description' => [ 'JOIN',
+                                                       'comment_img_description.comment_id = temp_img_description.imgcomment_description_id' ],
+                                       ],
+                               ],
+                       ],
+               ];
+       }
+
+       private function assertComment( $expect, $actual, $from ) {
+               $this->assertSame( $expect['text'], $actual->text, "text $from" );
+               $this->assertInstanceOf( get_class( $expect['message'] ), $actual->message,
+                       "message class $from" );
+               $this->assertSame( $expect['message']->getKeysToTry(), $actual->message->getKeysToTry(),
+                       "message keys $from" );
+               $this->assertEquals( $expect['message']->text(), $actual->message->text(),
+                       "message rendering $from" );
+               $this->assertEquals( $expect['data'], $actual->data, "data $from" );
+       }
+
+       /**
+        * @dataProvider provideInsertRoundTrip
+        * @param string $table
+        * @param string $key
+        * @param string $pk
+        * @param string $extraFields
+        * @param string|Message $comment
+        * @param array|null $data
+        * @param array $expect
+        */
+       public function testInsertRoundTrip( $table, $key, $pk, $extraFields, $comment, $data, $expect ) {
+               $expectOld = [
+                       'text' => $expect['text'],
+                       'message' => new RawMessage( '$1', [ $expect['text'] ] ),
+                       'data' => null,
+               ];
+
+               $stages = [
+                       MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
+                       MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
+                       MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+                       MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+               ];
+
+               foreach ( $stages as $writeStage => $readRange ) {
+                       if ( $key === 'ipb_reason' ) {
+                               $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
+                       }
+
+                       $wstore = $this->makeStore( $writeStage, $key );
+                       $usesTemp = $key === 'rev_comment';
+
+                       if ( $usesTemp ) {
+                               list( $fields, $callback ) = $wstore->insertWithTempTable( $this->db, $comment, $data );
+                       } else {
+                               $fields = $wstore->insert( $this->db, $comment, $data );
+                       }
+
+                       if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+                               $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" );
+                       } else {
+                               $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
+                       }
+                       if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+                               $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
+                       } else {
+                               $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
+                       }
+
+                       $extraFields[$pk] = $this->db->nextSequenceValue( "{$table}_{$pk}_seq" );
+                       $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
+                       $id = $this->db->insertId();
+                       if ( $usesTemp ) {
+                               $callback( $id );
+                       }
+
+                       for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+                               $rstore = $this->makeStore( $readStage, $key );
+
+                               $fieldRow = $this->db->selectRow(
+                                       $table,
+                                       $rstore->getFields(),
+                                       [ $pk => $id ],
+                                       __METHOD__
+                               );
+
+                               $queryInfo = $rstore->getJoin();
+                               $joinRow = $this->db->selectRow(
+                                       [ $table ] + $queryInfo['tables'],
+                                       $queryInfo['fields'],
+                                       [ $pk => $id ],
+                                       __METHOD__,
+                                       [],
+                                       $queryInfo['joins']
+                               );
+
+                               $this->assertComment(
+                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $rstore->getCommentLegacy( $this->db, $fieldRow ),
+                                       "w=$writeStage, r=$readStage, from getFields()"
+                               );
+                               $this->assertComment(
+                                       $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect,
+                                       $rstore->getComment( $joinRow ),
+                                       "w=$writeStage, r=$readStage, from getJoin()"
+                               );
+                       }
+               }
+       }
+
+       public static function provideInsertRoundTrip() {
+               $db = wfGetDB( DB_REPLICA ); // for timestamps
+
+               $msgComment = new Message( 'parentheses', [ 'message comment' ] );
+               $textCommentMsg = new RawMessage( '$1', [ 'text comment' ] );
+               $nestedMsgComment = new Message( [ 'parentheses', 'rawmessage' ], [ new Message( 'mainpage' ) ] );
+               $ipbfields = [
+                       'ipb_range_start' => '',
+                       'ipb_range_end' => '',
+                       'ipb_by' => 0,
+                       'ipb_timestamp' => $db->timestamp(),
+                       'ipb_expiry' => $db->getInfinity(),
+               ];
+               $revfields = [
+                       'rev_page' => 42,
+                       'rev_text_id' => 42,
+                       'rev_len' => 0,
+                       'rev_user' => 0,
+                       'rev_user_text' => '',
+                       'rev_timestamp' => $db->timestamp(),
+               ];
+               $comStoreComment = new CommentStoreComment(
+                       null, 'comment store comment', null, [ 'foo' => 'bar' ]
+               );
+
+               return [
+                       'Simple table, text comment' => [
+                               'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, 'text comment', null, [
+                                       'text' => 'text comment',
+                                       'message' => $textCommentMsg,
+                                       'data' => null,
+                               ]
+                       ],
+                       'Simple table, text comment with data' => [
+                               'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, 'text comment', [ 'message' => 42 ], [
+                                       'text' => 'text comment',
+                                       'message' => $textCommentMsg,
+                                       'data' => [ 'message' => 42 ],
+                               ]
+                       ],
+                       'Simple table, message comment' => [
+                               'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $msgComment, null, [
+                                       'text' => '(message comment)',
+                                       'message' => $msgComment,
+                                       'data' => null,
+                               ]
+                       ],
+                       'Simple table, message comment with data' => [
+                               'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $msgComment, [ 'message' => 42 ], [
+                                       'text' => '(message comment)',
+                                       'message' => $msgComment,
+                                       'data' => [ 'message' => 42 ],
+                               ]
+                       ],
+                       'Simple table, nested message comment' => [
+                               'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, $nestedMsgComment, null, [
+                                       'text' => '(Main Page)',
+                                       'message' => $nestedMsgComment,
+                                       'data' => null,
+                               ]
+                       ],
+                       'Simple table, CommentStoreComment' => [
+                               'ipblocks', 'ipb_reason', 'ipb_id', $ipbfields, clone $comStoreComment, [ 'baz' => 'baz' ], [
+                                       'text' => 'comment store comment',
+                                       'message' => $comStoreComment->message,
+                                       'data' => [ 'foo' => 'bar' ],
+                               ]
+                       ],
+
+                       'Revision, text comment' => [
+                               'revision', 'rev_comment', 'rev_id', $revfields, 'text comment', null, [
+                                       'text' => 'text comment',
+                                       'message' => $textCommentMsg,
+                                       'data' => null,
+                               ]
+                       ],
+                       'Revision, text comment with data' => [
+                               'revision', 'rev_comment', 'rev_id', $revfields, 'text comment', [ 'message' => 42 ], [
+                                       'text' => 'text comment',
+                                       'message' => $textCommentMsg,
+                                       'data' => [ 'message' => 42 ],
+                               ]
+                       ],
+                       'Revision, message comment' => [
+                               'revision', 'rev_comment', 'rev_id', $revfields, $msgComment, null, [
+                                       'text' => '(message comment)',
+                                       'message' => $msgComment,
+                                       'data' => null,
+                               ]
+                       ],
+                       'Revision, message comment with data' => [
+                               'revision', 'rev_comment', 'rev_id', $revfields, $msgComment, [ 'message' => 42 ], [
+                                       'text' => '(message comment)',
+                                       'message' => $msgComment,
+                                       'data' => [ 'message' => 42 ],
+                               ]
+                       ],
+                       'Revision, nested message comment' => [
+                               'revision', 'rev_comment', 'rev_id', $revfields, $nestedMsgComment, null, [
+                                       'text' => '(Main Page)',
+                                       'message' => $nestedMsgComment,
+                                       'data' => null,
+                               ]
+                       ],
+                       'Revision, CommentStoreComment' => [
+                               'revision', 'rev_comment', 'rev_id', $revfields, clone $comStoreComment, [ 'baz' => 'baz' ], [
+                                       'text' => 'comment store comment',
+                                       'message' => $comStoreComment->message,
+                                       'data' => [ 'foo' => 'bar' ],
+                               ]
+                       ],
+               ];
+       }
+
+       public function testGetCommentErrors() {
+               MediaWiki\suppressWarnings();
+               $reset = new ScopedCallback( 'MediaWiki\restoreWarnings' );
+
+               $store = $this->makeStore( MIGRATION_OLD, 'dummy' );
+               $res = $store->getComment( [ 'dummy' => 'comment' ] );
+               $this->assertSame( '', $res->text );
+               $res = $store->getComment( [ 'dummy' => 'comment' ], true );
+               $this->assertSame( 'comment', $res->text );
+
+               $store = $this->makeStore( MIGRATION_NEW, 'dummy' );
+               try {
+                       $store->getComment( [ 'dummy' => 'comment' ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame( '$row does not contain fields needed for comment dummy', $ex->getMessage() );
+               }
+               $res = $store->getComment( [ 'dummy' => 'comment' ], true );
+               $this->assertSame( 'comment', $res->text );
+               try {
+                       $store->getComment( [ 'dummy_id' => 1 ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               '$row does not contain fields needed for comment dummy and getComment(), '
+                               . 'but does have fields for getCommentLegacy()',
+                               $ex->getMessage()
+                       );
+               }
+
+               $store = $this->makeStore( MIGRATION_NEW, 'rev_comment' );
+               try {
+                       $store->getComment( [ 'rev_comment' => 'comment' ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               '$row does not contain fields needed for comment rev_comment', $ex->getMessage()
+                       );
+               }
+               $res = $store->getComment( [ 'rev_comment' => 'comment' ], true );
+               $this->assertSame( 'comment', $res->text );
+               try {
+                       $store->getComment( [ 'rev_comment_pk' => 1 ] );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+                       $this->assertSame(
+                               '$row does not contain fields needed for comment rev_comment and getComment(), '
+                               . 'but does have fields for getCommentLegacy()',
+                               $ex->getMessage()
+                       );
+               }
+       }
+
+       public static function provideStages() {
+               return [
+                       'MIGRATION_OLD' => [ MIGRATION_OLD ],
+                       'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
+                       'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
+                       'MIGRATION_NEW' => [ MIGRATION_NEW ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideStages
+        * @param int $stage
+        * @expectedException InvalidArgumentException
+        * @expectedExceptionMessage Must use insertWithTempTable() for rev_comment
+        */
+       public function testInsertWrong( $stage ) {
+               $store = $this->makeStore( $stage, 'rev_comment' );
+               $store->insert( $this->db, 'foo' );
+       }
+
+       /**
+        * @dataProvider provideStages
+        * @param int $stage
+        * @expectedException InvalidArgumentException
+        * @expectedExceptionMessage Must use insert() for ipb_reason
+        */
+       public function testInsertWithTempTableWrong( $stage ) {
+               $store = $this->makeStore( $stage, 'ipb_reason' );
+               $store->insertWithTempTable( $this->db, 'foo' );
+       }
+
+       /**
+        * @dataProvider provideStages
+        * @param int $stage
+        */
+       public function testInsertWithTempTableDeprecated( $stage ) {
+               $wrap = TestingAccessWrapper::newFromClass( CommentStore::class );
+               $wrap->formerTempTables += [ 'ipb_reason' => '1.30' ];
+
+               $this->hideDeprecated( 'CommentStore::insertWithTempTable for ipb_reason' );
+               $store = $this->makeStore( $stage, 'ipb_reason' );
+               list( $fields, $callback ) = $store->insertWithTempTable( $this->db, 'foo' );
+               $this->assertTrue( is_callable( $callback ) );
+       }
+
+       public function testConstructor() {
+               $this->assertInstanceOf( CommentStore::class, CommentStore::newKey( 'dummy' ) );
+       }
+
+}
index 3ba82a6..b207e06 100644 (file)
@@ -146,7 +146,7 @@ class RevisionStorageTest extends MediaWikiTestCase {
                $orig = $this->makeRevision();
 
                $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select( 'revision', '*', [ 'rev_id' => $orig->getId() ] );
+               $res = $dbr->select( 'revision', Revision::selectFields(), [ 'rev_id' => $orig->getId() ] );
                $this->assertTrue( is_object( $res ), 'query failed' );
 
                $row = $res->fetchObject();
@@ -164,7 +164,7 @@ class RevisionStorageTest extends MediaWikiTestCase {
                $orig = $this->makeRevision();
 
                $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select( 'revision', '*', [ 'rev_id' => $orig->getId() ] );
+               $res = $dbr->select( 'revision', Revision::selectFields(), [ 'rev_id' => $orig->getId() ] );
                $this->assertTrue( is_object( $res ), 'query failed' );
 
                $row = $res->fetchObject();
@@ -188,7 +188,9 @@ class RevisionStorageTest extends MediaWikiTestCase {
                $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
 
                $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select( 'archive', '*', [ 'ar_rev_id' => $orig->getId() ] );
+               $res = $dbr->select(
+                       'archive', Revision::selectArchiveFields(), [ 'ar_rev_id' => $orig->getId() ]
+               );
                $this->assertTrue( is_object( $res ), 'query failed' );
 
                $row = $res->fetchObject();
index 4744875..62ba5f6 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use Wikimedia\ScopedCallback;
 use Wikimedia\TestingAccessWrapper;
 
 /**
@@ -447,100 +448,175 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_FLAGS ] ],
                                null,
+                               [],
                                [ 'rc_type', 'rc_minor', 'rc_bot' ],
                                [],
                                [],
+                               [],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
                                null,
+                               [],
                                [ 'rc_user_text' ],
                                [],
                                [],
+                               [],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
                                null,
+                               [],
                                [ 'rc_user' ],
                                [],
                                [],
+                               [],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
+                               null,
+                               [],
+                               [
+                                       'rc_comment_text' => 'rc_comment',
+                                       'rc_comment_data' => 'NULL',
+                                       'rc_comment_cid' => 'NULL',
+                               ],
+                               [],
+                               [],
+                               [],
+                               [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD ],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
+                               null,
+                               [ 'comment_rc_comment' => 'comment' ],
+                               [
+                                       'rc_comment_text' => 'COALESCE( comment_rc_comment.comment_text, rc_comment )',
+                                       'rc_comment_data' => 'comment_rc_comment.comment_data',
+                                       'rc_comment_cid' => 'comment_rc_comment.comment_id',
+                               ],
+                               [],
+                               [],
+                               [ 'comment_rc_comment' => [ 'LEFT JOIN', 'comment_rc_comment.comment_id = rc_comment_id' ] ],
+                               [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH ],
+                       ],
+                       [
+                               [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
+                               null,
+                               [ 'comment_rc_comment' => 'comment' ],
+                               [
+                                       'rc_comment_text' => 'COALESCE( comment_rc_comment.comment_text, rc_comment )',
+                                       'rc_comment_data' => 'comment_rc_comment.comment_data',
+                                       'rc_comment_cid' => 'comment_rc_comment.comment_id',
+                               ],
+                               [],
+                               [],
+                               [ 'comment_rc_comment' => [ 'LEFT JOIN', 'comment_rc_comment.comment_id = rc_comment_id' ] ],
+                               [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_WRITE_NEW ],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
                                null,
-                               [ 'rc_comment' ],
+                               [ 'comment_rc_comment' => 'comment' ],
+                               [
+                                       'rc_comment_text' => 'comment_rc_comment.comment_text',
+                                       'rc_comment_data' => 'comment_rc_comment.comment_data',
+                                       'rc_comment_cid' => 'comment_rc_comment.comment_id',
+                               ],
                                [],
                                [],
+                               [ 'comment_rc_comment' => [ 'JOIN', 'comment_rc_comment.comment_id = rc_comment_id' ] ],
+                               [ 'wgCommentTableSchemaMigrationStage' => MIGRATION_NEW ],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ],
                                null,
+                               [],
                                [ 'rc_patrolled', 'rc_log_type' ],
                                [],
                                [],
+                               [],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_SIZES ] ],
                                null,
+                               [],
                                [ 'rc_old_len', 'rc_new_len' ],
                                [],
                                [],
+                               [],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_LOG_INFO ] ],
                                null,
+                               [],
                                [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
                                [],
                                [],
+                               [],
                        ],
                        [
                                [ 'namespaceIds' => [ 0, 1 ] ],
                                null,
                                [],
+                               [],
                                [ 'wl_namespace' => [ 0, 1 ] ],
                                [],
+                               [],
                        ],
                        [
                                [ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ],
                                null,
                                [],
+                               [],
                                [ 'wl_namespace' => [ 0, 1 ] ],
                                [],
+                               [],
                        ],
                        [
                                [ 'rcTypes' => [ RC_EDIT, RC_NEW ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_type' => [ RC_EDIT, RC_NEW ] ],
                                [],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
                                null,
                                [],
                                [],
-                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                               [],
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_NEWER ],
                                null,
                                [],
                                [],
-                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                               [],
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'start' => '20151212010101' ],
                                null,
                                [],
+                               [],
                                [ "rc_timestamp <= '20151212010101'" ],
-                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'end' => '20151212010101' ],
                                null,
                                [],
+                               [],
                                [ "rc_timestamp >= '20151212010101'" ],
-                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                               [],
                        ],
                        [
                                [
@@ -550,22 +626,28 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
                                ],
                                null,
                                [],
+                               [],
                                [ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ],
-                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
+                               [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'start' => '20151212010101' ],
                                null,
                                [],
+                               [],
                                [ "rc_timestamp >= '20151212010101'" ],
-                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'end' => '20151212010101' ],
                                null,
                                [],
+                               [],
                                [ "rc_timestamp <= '20151212010101'" ],
-                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
+                               [],
                        ],
                        [
                                [
@@ -575,133 +657,169 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
                                ],
                                null,
                                [],
+                               [],
                                [ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ],
-                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
+                               [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
+                               [],
                        ],
                        [
                                [ 'limit' => 10 ],
                                null,
                                [],
                                [],
+                               [],
                                [ 'LIMIT' => 11 ],
+                               [],
                        ],
                        [
                                [ 'limit' => "10; DROP TABLE watchlist;\n--" ],
                                null,
                                [],
                                [],
+                               [],
                                [ 'LIMIT' => 11 ],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_MINOR ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_minor != 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_MINOR ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_minor = 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_BOT ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_bot != 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_BOT ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_bot = 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_user = 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_user != 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_patrolled != 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_PATROLLED ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_patrolled = 0' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_UNREAD ] ],
                                null,
                                [],
+                               [],
                                [ 'rc_timestamp >= wl_notificationtimestamp' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_UNREAD ] ],
                                null,
                                [],
+                               [],
                                [ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'onlyByUser' => 'SomeOtherUser' ],
                                null,
                                [],
+                               [],
                                [ 'rc_user_text' => 'SomeOtherUser' ],
                                [],
+                               [],
                        ],
                        [
                                [ 'notByUser' => 'SomeOtherUser' ],
                                null,
                                [],
+                               [],
                                [ "rc_user_text != 'SomeOtherUser'" ],
                                [],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
                                [ '20151212010101', 123 ],
                                [],
+                               [],
                                [
                                        "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
                                ],
                                [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_NEWER ],
                                [ '20151212010101', 123 ],
                                [],
+                               [],
                                [
                                        "(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))"
                                ],
                                [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
+                               [],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
                                [ '20151212010101', "123; DROP TABLE watchlist;\n--" ],
                                [],
+                               [],
                                [
                                        "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
                                ],
                                [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
+                               [],
                        ],
                ];
        }
@@ -712,10 +830,28 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
        public function testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult(
                array $options,
                $startFrom,
+               array $expectedExtraTables,
                array $expectedExtraFields,
                array $expectedExtraConds,
-               array $expectedDbOptions
+               array $expectedDbOptions,
+               array $expectedExtraJoinConds,
+               array $globals = []
        ) {
+               // Sigh. This test class doesn't extend MediaWikiTestCase, so we have to reinvent setMwGlobals().
+               if ( $globals ) {
+                       $resetGlobals = [];
+                       foreach ( $globals as $k => $v ) {
+                               $resetGlobals[$k] = $GLOBALS[$k];
+                               $GLOBALS[$k] = $v;
+                       }
+                       $reset = new ScopedCallback( function () use ( $resetGlobals ) {
+                               foreach ( $resetGlobals as $k => $v ) {
+                                       $GLOBALS[$k] = $v;
+                               }
+                       } );
+               }
+
+               $expectedTables = array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables );
                $expectedFields = array_merge(
                        [
                                'rc_id',
@@ -736,29 +872,33 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
                        [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)', ],
                        $expectedExtraConds
                );
+               $expectedJoinConds = array_merge(
+                       [
+                               'watchlist' => [
+                                       'INNER JOIN',
+                                       [
+                                               'wl_namespace=rc_namespace',
+                                               'wl_title=rc_title'
+                                       ]
+                               ],
+                               'page' => [
+                                       'LEFT JOIN',
+                                       'rc_cur_id=page_id',
+                               ],
+                       ],
+                       $expectedExtraJoinConds
+               );
 
                $mockDb = $this->getMockDb();
                $mockDb->expects( $this->once() )
                        ->method( 'select' )
                        ->with(
-                               [ 'recentchanges', 'watchlist', 'page' ],
+                               $expectedTables,
                                $expectedFields,
                                $expectedConds,
                                $this->isType( 'string' ),
                                $expectedDbOptions,
-                               [
-                                       'watchlist' => [
-                                               'INNER JOIN',
-                                               [
-                                                       'wl_namespace=rc_namespace',
-                                                       'wl_title=rc_title'
-                                               ]
-                                       ],
-                                       'page' => [
-                                               'LEFT JOIN',
-                                               'rc_cur_id=page_id',
-                                       ],
-                               ]
+                               $expectedJoinConds
                        )
                        ->will( $this->returnValue( [] ) );
 
index 4f4453f..fdbeded 100644 (file)
@@ -1074,6 +1074,8 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
                        'rc_user' => 0,
                        'rc_user_text' => 'External User',
                        'rc_comment' => '',
+                       'rc_comment_text' => '',
+                       'rc_comment_data' => null,
                        'rc_this_oldid' => $title->getLatestRevID(),
                        'rc_last_oldid' => $title->getLatestRevID(),
                        'rc_bot' => 0,
index 68f9079..d638d0f 100644 (file)
@@ -31,6 +31,8 @@ class RecentChangeTest extends MediaWikiTestCase {
                $row->rc_foo = 'AAA';
                $row->rc_timestamp = '20150921134808';
                $row->rc_deleted = 'bar';
+               $row->rc_comment_text = 'comment';
+               $row->rc_comment_data = null;
 
                $rc = RecentChange::newFromRow( $row );
 
@@ -38,6 +40,29 @@ class RecentChangeTest extends MediaWikiTestCase {
                        'rc_foo' => 'AAA',
                        'rc_timestamp' => '20150921134808',
                        'rc_deleted' => 'bar',
+                       'rc_comment' => 'comment',
+                       'rc_comment_text' => 'comment',
+                       'rc_comment_data' => null,
+               ];
+               $this->assertEquals( $expected, $rc->getAttributes() );
+
+               $row = new stdClass();
+               $row->rc_foo = 'AAA';
+               $row->rc_timestamp = '20150921134808';
+               $row->rc_deleted = 'bar';
+               $row->rc_comment = 'comment';
+
+               MediaWiki\suppressWarnings();
+               $rc = RecentChange::newFromRow( $row );
+               MediaWiki\restoreWarnings();
+
+               $expected = [
+                       'rc_foo' => 'AAA',
+                       'rc_timestamp' => '20150921134808',
+                       'rc_deleted' => 'bar',
+                       'rc_comment' => 'comment',
+                       'rc_comment_text' => 'comment',
+                       'rc_comment_data' => null,
                ];
                $this->assertEquals( $expected, $rc->getAttributes() );
        }
index 4da09d8..2c30948 100644 (file)
@@ -119,6 +119,8 @@ class TestRecentChangesHelper {
                                'rc_last_oldid' => $lastid,
                                'rc_cur_id' => $curid,
                                'rc_comment' => '[[:Testpage]] added to category',
+                               'rc_comment_text' => '[[:Testpage]] added to category',
+                               'rc_comment_data' => null,
                                'rc_old_len' => 0,
                                'rc_new_len' => 0,
                        ]
@@ -139,6 +141,8 @@ class TestRecentChangesHelper {
                        'rc_old_len' => 212,
                        'rc_new_len' => 188,
                        'rc_comment' => '',
+                       'rc_comment_text' => '',
+                       'rc_comment_data' => null,
                        'rc_minor' => 0,
                        'rc_bot' => 0,
                        'rc_type' => 0,
index c13cf25..ebe1972 100644 (file)
@@ -383,7 +383,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
                                        false // retry
                                ],
                        ],
-                       '200 OK - Skip dir' => [
+                       '200 OK - Empty dir' => [
                                'http' => [
                                        'code' => 200,
                                        'reason' => 'OK',
@@ -395,7 +395,8 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
                                                ],
                                                [
                                                        'key' => '/example/sub',
-                                                       'dir' => true
+                                                       'dir' => true,
+                                                       'nodes' => [],
                                                ],
                                                [
                                                        'key' => '/example/bar',
@@ -410,6 +411,68 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
                                        false // retry
                                ],
                        ],
+                       '200 OK - Recursive' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [],
+                                       'body' => json_encode( [ 'node' => [ 'nodes' => [
+                                               [
+                                                       'key' => '/example/a',
+                                                       'dir' => true,
+                                                       'nodes' => [
+                                                               [
+                                                                       'key' => 'b',
+                                                                       'value' => json_encode( [ 'val' => true ] ),
+                                                               ],
+                                                               [
+                                                                       'key' => 'c',
+                                                                       'value' => json_encode( [ 'val' => false ] ),
+                                                               ],
+                                                       ],
+                                               ],
+                                       ] ] ] ),
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       [ 'a/b' => true, 'a/c' => false ], // data
+                                       null,
+                                       false // retry
+                               ],
+                       ],
+                       '200 OK - Missing nodes at second level' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [],
+                                       'body' => json_encode( [ 'node' => [ 'nodes' => [
+                                               [
+                                                       'key' => '/example/a',
+                                                       'dir' => true,
+                                               ],
+                                       ] ] ] ),
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       null,
+                                       "Unexpected JSON response in dir 'a'; missing 'nodes' list.",
+                                       false // retry
+                               ],
+                       ],
+                       '200 OK - Correctly encoded garbage response' => [
+                               'http' => [
+                                       'code' => 200,
+                                       'reason' => 'OK',
+                                       'headers' => [],
+                                       'body' => json_encode( [ 'foo' => 'bar' ] ),
+                                       'error' => '',
+                               ],
+                               'expect' => [
+                                       null,
+                                       "Unexpected JSON response: Missing or invalid node at top level.",
+                                       false // retry
+                               ],
+                       ],
                        '200 OK - Bad value' => [
                                'http' => [
                                        'code' => 200,
@@ -453,7 +516,7 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
                                ],
                                'expect' => [
                                        null, // data
-                                       "Unexpected JSON response; missing 'nodes' list.",
+                                       "Error unserializing JSON response.",
                                        false // retry
                                ],
                        ],
@@ -491,6 +554,9 @@ class EtcConfigTest extends PHPUnit_Framework_TestCase {
        /**
         * @covers EtcdConfig::fetchAllFromEtcdServer
         * @covers EtcdConfig::unserialize
+        * @covers EtcdConfig::parseResponse
+        * @covers EtcdConfig::parseDirectory
+        * @covers EtcdConfigParseError
         * @dataProvider provideFetchFromServer
         */
        public function testFetchFromServer( array $httpResponse, array $expected ) {
index 639c323..4c0a5fa 100644 (file)
@@ -378,16 +378,33 @@ class LinksUpdateTest extends MediaWikiLangTestCase {
        protected function assertRecentChangeByCategorization(
                Title $pageTitle, ParserOutput $parserOutput, Title $categoryTitle, $expectedRows
        ) {
-               $this->assertSelect(
-                       'recentchanges',
-                       'rc_title, rc_comment',
-                       [
-                               'rc_type' => RC_CATEGORIZE,
-                               'rc_namespace' => NS_CATEGORY,
-                               'rc_title' => $categoryTitle->getDBkey()
-                       ],
-                       $expectedRows
-               );
+               global $wgCommentTableSchemaMigrationStage;
+
+               if ( $wgCommentTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                       $this->assertSelect(
+                               'recentchanges',
+                               'rc_title, rc_comment',
+                               [
+                                       'rc_type' => RC_CATEGORIZE,
+                                       'rc_namespace' => NS_CATEGORY,
+                                       'rc_title' => $categoryTitle->getDBkey()
+                               ],
+                               $expectedRows
+                       );
+               }
+               if ( $wgCommentTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                       $this->assertSelect(
+                               [ 'recentchanges', 'comment' ],
+                               'rc_title, comment_text',
+                               [
+                                       'rc_type' => RC_CATEGORIZE,
+                                       'rc_namespace' => NS_CATEGORY,
+                                       'rc_title' => $categoryTitle->getDBkey(),
+                                       'comment_id = rc_comment_id',
+                               ],
+                               $expectedRows
+                       );
+               }
        }
 
        private function runAllRelatedJobs() {
index 307652d..e47c4ba 100644 (file)
@@ -237,7 +237,7 @@ class IPTest extends PHPUnit_Framework_TestCase {
                ];
                foreach ( $ipCIDRs as $i ) {
                        $this->assertFalse( IP::isValid( $i ),
-                               "$i is an invalid IP address because it is a block" );
+                               "$i is an invalid IP address because it is a range" );
                }
                // Incomplete/garbage
                $invalid = [
@@ -254,9 +254,9 @@ class IPTest extends PHPUnit_Framework_TestCase {
        }
 
        /**
-        * Provide some valid IP blocks
+        * Provide some valid IP ranges
         */
-       public function provideValidBlocks() {
+       public function provideValidRanges() {
                return [
                        [ '116.17.184.5/32' ],
                        [ '0.17.184.5/30' ],
@@ -274,22 +274,22 @@ class IPTest extends PHPUnit_Framework_TestCase {
        }
 
        /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideValidBlocks
+        * @covers IP::isValidRange
+        * @dataProvider provideValidRanges
         */
-       public function testValidBlocks( $block ) {
-               $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
+       public function testValidRanges( $range ) {
+               $this->assertTrue( IP::isValidRange( $range ), "$range is a valid IP range" );
        }
 
        /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideInvalidBlocks
+        * @covers IP::isValidRange
+        * @dataProvider provideInvalidRanges
         */
-       public function testInvalidBlocks( $invalid ) {
-               $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
+       public function testInvalidRanges( $invalid ) {
+               $this->assertFalse( IP::isValidRange( $invalid ), "$invalid is not a valid IP range" );
        }
 
-       public function provideInvalidBlocks() {
+       public function provideInvalidRanges() {
                return [
                        [ '116.17.184.5/33' ],
                        [ '0.17.184.5/130' ],
index c289839..2dc9a2c 100644 (file)
@@ -39,7 +39,8 @@ abstract class LogFormatterTestCase extends MediaWikiLangTestCase {
                        'log_namespace' => isset( $data['namespace'] ) ? $data['namespace'] : NS_MAIN,
                        'log_title' => isset( $data['title'] ) ? $data['title'] : 'Main_Page',
                        'log_page' => isset( $data['page'] ) ? $data['page'] : 0,
-                       'log_comment' => isset( $data['comment'] ) ? $data['comment'] : '',
+                       'log_comment_text' => isset( $data['comment'] ) ? $data['comment'] : '',
+                       'log_comment_data' => null,
                        'log_params' => $legacy
                                ? LogPage::makeParamBlob( $data['params'] )
                                : LogEntryBase::makeParamBlob( $data['params'] ),
index a9f74b6..d0fefde 100644 (file)
@@ -17,6 +17,7 @@ class WikiPageTest extends MediaWikiLangTestCase {
                        $this->tablesUsed,
                        [ 'page',
                                'revision',
+                               'archive',
                                'text',
 
                                'recentchanges',
@@ -1123,4 +1124,84 @@ more stuff
                $page = WikiPage::factory( $title );
                $this->assertEquals( 'WikiPage', get_class( $page ) );
        }
+
+       /**
+        * @dataProvider provideCommentMigrationOnDeletion
+        * @param int $wstage
+        * @param int $rstage
+        */
+       public function testCommentMigrationOnDeletion( $wstage, $rstage ) {
+               $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $wstage );
+               $dbr = wfGetDB( DB_REPLICA );
+
+               $page = $this->createPage(
+                       "WikiPageTest_testCommentMigrationOnDeletion",
+                       "foo",
+                       CONTENT_MODEL_WIKITEXT
+               );
+               $revid = $page->getLatest();
+               if ( $wstage > MIGRATION_OLD ) {
+                       $comment_id = $dbr->selectField(
+                               'revision_comment_temp',
+                               'revcomment_comment_id',
+                               [ 'revcomment_rev' => $revid ],
+                               __METHOD__
+                       );
+               }
+
+               $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $rstage );
+
+               $page->doDeleteArticle( "testing deletion" );
+
+               if ( $rstage > MIGRATION_OLD ) {
+                       // Didn't leave behind any 'revision_comment_temp' rows
+                       $n = $dbr->selectField(
+                               'revision_comment_temp', 'COUNT(*)', [ 'revcomment_rev' => $revid ], __METHOD__
+                       );
+                       $this->assertEquals( 0, $n, 'no entry in revision_comment_temp after deletion' );
+
+                       // Copied or upgraded the comment_id, as applicable
+                       $ar_comment_id = $dbr->selectField(
+                               'archive',
+                               'ar_comment_id',
+                               [ 'ar_rev_id' => $revid ],
+                               __METHOD__
+                       );
+                       if ( $wstage > MIGRATION_OLD ) {
+                               $this->assertSame( $comment_id, $ar_comment_id );
+                       } else {
+                               $this->assertNotEquals( 0, $ar_comment_id );
+                       }
+               }
+
+               // Copied rev_comment, if applicable
+               if ( $rstage <= MIGRATION_WRITE_BOTH && $wstage <= MIGRATION_WRITE_BOTH ) {
+                       $ar_comment = $dbr->selectField(
+                               'archive',
+                               'ar_comment',
+                               [ 'ar_rev_id' => $revid ],
+                               __METHOD__
+                       );
+                       $this->assertSame( 'testing', $ar_comment );
+               }
+       }
+
+       public static function provideCommentMigrationOnDeletion() {
+               return [
+                       [ MIGRATION_OLD, MIGRATION_OLD ],
+                       [ MIGRATION_OLD, MIGRATION_WRITE_BOTH ],
+                       [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
+                       [ MIGRATION_WRITE_BOTH, MIGRATION_OLD ],
+                       [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_BOTH ],
+                       [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
+                       [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+                       [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_BOTH ],
+                       [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_NEW ],
+                       [ MIGRATION_WRITE_NEW, MIGRATION_NEW ],
+                       [ MIGRATION_NEW, MIGRATION_WRITE_BOTH ],
+                       [ MIGRATION_NEW, MIGRATION_WRITE_NEW ],
+                       [ MIGRATION_NEW, MIGRATION_NEW ],
+               ];
+       }
+
 }
index 09052f3..4ea1090 100644 (file)
@@ -82,15 +82,15 @@ class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
 
                # Filter out .txt files
                $files = ParserTestRunner::getParserTestFiles();
-               foreach ( $files as $parserTestFile ) {
+               foreach ( $files as $extName => $parserTestFile ) {
                        $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) );
 
                        if ( $isCore && $wantsCore ) {
                                self::debug( "included core parser tests: $parserTestFile" );
-                               $filesToTest[] = $parserTestFile;
+                               $filesToTest[$extName] = $parserTestFile;
                        } elseif ( !$isCore && $wantsRest ) {
                                self::debug( "included non core parser tests: $parserTestFile" );
-                               $filesToTest[] = $parserTestFile;
+                               $filesToTest[$extName] = $parserTestFile;
                        } else {
                                self::debug( "skipped parser tests: $parserTestFile" );
                        }
@@ -100,12 +100,13 @@ class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
 
                $testList = [];
                $counter = 0;
-               foreach ( $filesToTest as $fileName ) {
-                       // Call the highest level directory the extension name.
-                       // It may or may not actually be, but it should be close
-                       // enough to cause there to be separate names for different
-                       // things, which is good enough for our purposes.
-                       $extensionName = basename( dirname( $fileName ) );
+               foreach ( $filesToTest as $extensionName => $fileName ) {
+                       if ( is_int( $extensionName ) ) {
+                               // If there's no extension name because this is coming
+                               // from the legacy global, then assume the next level directory
+                               // is the extension name (e.g. extensions/FooBar/parserTests.txt).
+                               $extensionName = basename( dirname( $fileName ) );
+                       }
                        $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
                        $parserTestClassName = ucfirst( $testsName );
 
index f3e4877..32a7712 100644 (file)
@@ -48,7 +48,8 @@ exports.config = {
        specs: [
                relPath( './tests/selenium/specs/**/*.js' ),
                relPath( './extensions/*/tests/selenium/specs/**/*.js' ),
-               relPath( './extensions/VisualEditor/modules/ve-mw/tests/selenium/specs/**/*.js' )
+               relPath( './extensions/VisualEditor/modules/ve-mw/tests/selenium/specs/**/*.js' ),
+               relPath( './skins/*/tests/selenium/specs/**/*.js' ),
        ],
        // Patterns to exclude.
        exclude: [