Merge "RCFilters: rephrase the feedback link text"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 30 Aug 2017 18:00:38 +0000 (18:00 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 30 Aug 2017 18:00:38 +0000 (18:00 +0000)
96 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/Revision.php
includes/Title.php
includes/WatchedItemQueryService.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryFilearchive.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/changes/ChangesListBooleanFilter.php
includes/changes/RecentChange.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/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/es.json
includes/logging/LogEntry.php
includes/logging/LogPage.php
includes/page/PageArchive.php
includes/page/WikiPage.php
includes/rcfeed/IRCColourfulRCFeedFormatter.php
includes/revisiondelete/RevDelLogItem.php
includes/revisiondelete/RevDelLogList.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
languages/i18n/ais.json [new file with mode: 0644]
languages/i18n/be-tarask.json
languages/i18n/ce.json
languages/i18n/es.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gu.json
languages/i18n/hr.json
languages/i18n/hsb.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/lt.json
languages/i18n/ne.json
languages/i18n/nn.json
languages/i18n/or.json
languages/i18n/qqq.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/su.json
maintenance/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/migrateComments.php [new file with mode: 0644]
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-comment-table.sql [new file with mode: 0644]
maintenance/tables.sql
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.toolbar/images/ksh/LICENSE
tests/parser/ParserTestRunner.php
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/deferred/LinksUpdateTest.php
tests/phpunit/includes/logging/LogFormatterTestCase.php
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/suites/ParserTestTopLevelSuite.php

index c7270ee..c4c56e8 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 ===
 
index 8062387..3a2ae10 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',
@@ -982,6 +984,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',
index 05e97b9..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 );
        }
 
        /**
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 72a072d..40913bb 100644 (file)
@@ -2701,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?
@@ -3687,8 +3687,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',
@@ -3696,11 +3697,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',
@@ -3708,7 +3708,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 ) ) {
@@ -3717,7 +3720,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;
                        }
                }
 
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 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 729628e..0687a15 100644 (file)
@@ -2670,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 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 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..17881ef 100644 (file)
        "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:",
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 961cb48..913bd38 100644 (file)
@@ -73,13 +73,6 @@ class ChangesListBooleanFilter extends ChangesListFilter {
         */
        protected $activeValue;
 
-       /**
-        * Whether this filter is visible somewhere (legacy form or structured UI).
-        *
-        * @var bool $isVisible
-        */
-       protected $isVisible;
-
        /**
         * Create a new filter with the specified configuration.
         *
@@ -106,8 +99,6 @@ class ChangesListBooleanFilter extends ChangesListFilter {
         * * $filterDefinition['default'] bool Default
         * * $filterDefinition['activeValue'] bool This filter is considered active when
         *     its value is equal to its activeValue. Default is true.
-        * * $filterDefinition['isVisible'] bool This filter is visible in the legacy form or
-        *     structured UI. 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
@@ -150,12 +141,6 @@ class ChangesListBooleanFilter extends ChangesListFilter {
                } else {
                        $this->activeValue = true;
                }
-
-               if ( isset( $filterDefinition['isVisible'] ) ) {
-                       $this->isVisible = $filterDefinition['isVisible'];
-               } else {
-                       $this->isVisible = true;
-               }
        }
 
        /**
@@ -268,11 +253,4 @@ class ChangesListBooleanFilter extends ChangesListFilter {
 
                return $opts[ $this->getName() ] === $this->activeValue;
        }
-
-       /**
-        * @return bool Whether this filter is visible anywhere
-        */
-       public function isVisible() {
-               return $this->isVisible;
-       }
 }
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 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 7b51ed7..c591974 100644 (file)
@@ -326,6 +326,8 @@ class MysqlUpdater extends DatabaseUpdater {
                                '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 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 95014a4..e79dcb1 100644 (file)
@@ -190,6 +190,8 @@ class SqliteUpdater extends DatabaseUpdater {
                                '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 021aa5d..85b23fe 100644 (file)
        "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 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 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 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 d6eac32..6ee697e 100644 (file)
@@ -636,7 +636,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(),
index 94b36b9..0dd66b0 100644 (file)
@@ -151,7 +151,6 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                                [
                                        'name' => 'extended',
                                        'isReplacedInStructuredUi' => true,
-                                       'isVisible' => false,
                                        'activeValue' => false,
                                        'default' => $this->getUser()->getBoolOption( 'extendwatchlist' ),
                                        'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables,
@@ -334,8 +333,9 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        // unchecked boxes.
                        foreach ( $this->filterGroups as $filterGroup ) {
                                if ( $filterGroup instanceof ChangesListBooleanFilterGroup ) {
+                                       /** @var ChangesListBooleanFilter $filter */
                                        foreach ( $filterGroup->getFilters() as $filter ) {
-                                               if ( $filter->isVisible() ) {
+                                               if ( $filter->displaysOnUnstructuredUi() ) {
                                                        $allBooleansFalse[$filter->getName()] = false;
                                                }
                                        }
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']
                ];
        }
 
diff --git a/languages/i18n/ais.json b/languages/i18n/ais.json
new file mode 100644 (file)
index 0000000..e320fdb
--- /dev/null
@@ -0,0 +1,526 @@
+{
+       "@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",
+       "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",
+       "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",
+       "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",
+       "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",
+       "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",
+       "revdelete-show-file-submit": "hang",
+       "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.",
+       "mypreferences": "setin tu kanamuhan",
+       "prefs-rc": "capi a demaiday a sumad",
+       "searchresultshead": "kilim",
+       "prefs-searchoptions": "kilim",
+       "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]])",
+       "rcfilters-savedqueries-cancel-label": "palawpes",
+       "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-hide": "midimut",
+       "rcshowhidemine": "$1 mikawaway tu kalumyiti nu maku",
+       "rcshowhidemine-show": "paazih",
+       "rcshowhidemine-hide": "midimut",
+       "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",
+       "license": "sapabeli tu kinli a cedang",
+       "license-header": "sapabeli tu kinli a cedang",
+       "imgfile": "tangan",
+       "listfiles": "tangan-tangan misiket",
+       "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",
+       "nbytes": "$1 {{PLURAL:$1|wyiyincu}}",
+       "ncategories": "{{PLURAL:$1|kakuniza}}",
+       "nmembers": "$1 {{PLURAL:$1|ku mamikawaw}}",
+       "newpages": "baluhay kasabelih",
+       "move": "milimad",
+       "pager-older-n": "{{PLURAL:$1| kusa malumanay}}",
+       "apisandbox-continue": "palalid",
+       "booksources": "nu cudad atu laculaculan",
+       "booksources-search-legend": "mikilim ku cudad atu laculaculan",
+       "booksources-search": "kilim",
+       "log": "nasulitan nakawawan",
+       "allpages": "hamin nu kasabelih",
+       "allarticles": "hamin nu kasabelih",
+       "allpagessubmit": "mileku",
+       "categories": "kakuniza",
+       "linksearch-ok": "kilim",
+       "watchlist": "miazihay a piazihan tu sulit",
+       "mywatchlist": "miazihay a piazihan tu sulit",
+       "watch": "miazih",
+       "watchlist-hide": "midimut",
+       "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-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",
+       "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-exifversion": "Exif baziyong / sapad",
+       "exif-colorspace": "kalukulit salaedan",
+       "exif-datetimeoriginal": "kalunasulitan masangaay’ a demiad atu tuki",
+       "exif-datetimedigitized": "suwyihwaay a demiad atu tuki",
+       "exif-orientation-1": "tatungus",
+       "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",
+       "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-list-wrapper": "([[Special:Tags|{{PLURAL:$1|aazihen paya’}}]]: $2)",
+       "tags-active-yes": "hang",
+       "tags-active-no": "caay",
+       "permanentlink": "saluimengay misiket",
+       "htmlform-no": "caay",
+       "htmlform-yes": "hang",
+       "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",
+       "feedback-cancel": "palawpes",
+       "searchsuggest-search": "kilim {{SITENAME}}"
+}
index 18ccaa1..651d491 100644 (file)
        "redirect-lookup": "Шукаць паводле:",
        "redirect-value": "Значэньне:",
        "redirect-user": "Ідэнтыфікатару ўдзельніка",
-       "redirect-page": "Ідэнтыфікатар старонкі",
+       "redirect-page": "Ідэнтыфікатару старонкі",
        "redirect-revision": "Вэрсіі старонкі",
        "redirect-file": "Імя файла",
        "redirect-logid": "ID журнала",
        "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": "Уваход / стварэньне рахунку",
index efa97ee..c8990bd 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-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-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-user-experience-level-newcomer-label": "Керланиш",
        "rcfilters-filter-user-experience-level-learner-label": "Доьшуш берш",
        "rcfilters-filter-bots-label": "Бот",
+       "rcfilters-filter-humans-label": "Адам ду (бот яц)",
+       "rcfilters-filter-humans-description": "Декъашхоша дина нисдарш.",
        "rcfilters-filter-patrolled-label": "Патрулйина",
+       "rcfilters-filter-pageedits-label": "АгӀонан нисдарш",
+       "rcfilters-filter-pageedits-description": "Дийцарийн а, категорийн а чулацаман дина нисдарш...",
+       "rcfilters-filter-newpages-label": "АгӀонаш кхоллар",
+       "rcfilters-filter-newpages-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-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}} бина болу",
        "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 bea0be0..3c0bab0 100644 (file)
        "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",
        "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 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 fc5eb34..95f4836 100644 (file)
        "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.",
index 6bfcda3..add4680 100644 (file)
@@ -30,7 +30,8 @@
                        "Matma Rex",
                        "Bhatakati aatma",
                        "YmKavishwar",
-                       "Kevin Kovadia"
+                       "Kevin Kovadia",
+                       "Drashti4"
                ]
        },
        "tog-underline": "કડીઓની નીચે લીટી (અંડરલાઇન):",
        "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": "નીચે દર્શાવેલ યાદી એ કડીઓ બતાવે છે જેઓ નવા અને જૂના પુસ્તકો  વેચે છે , અને તમે માંગેલ વસ્તુ સંબંધિ વધુ મહિતી પણ ધરાવી શકે છે.",
        "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]], or [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "જાઓ",
        "redirect-lookup": "જુઓ:",
        "redirect-value": "કિંમત:",
index 0b67bc9..eba2522 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",
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 71c62d0..e7a8c1e 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 link diretti segui/non seguire alle voci negli Osservati speciali (richiesto JavaScript per attivare la funzionalità)",
+       "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",
        "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 0efcb3e..5c9498a 100644 (file)
        "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 0b5e469..ed81401 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.",