Merge "Use header_register_callback to avoid caching responses with Set-Cookie headers"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 8 Mar 2016 21:21:21 +0000 (21:21 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 8 Mar 2016 21:21:21 +0000 (21:21 +0000)
87 files changed:
RELEASE-NOTES-1.27
docs/hooks.txt
includes/EditPage.php
includes/GlobalFunctions.php
includes/MovePage.php
includes/OutputPage.php
includes/Title.php
includes/WatchedItem.php
includes/WatchedItemStore.php
includes/actions/HistoryAction.php
includes/actions/RollbackAction.php
includes/actions/WatchAction.php
includes/api/ApiBase.php
includes/api/ApiContinuationManager.php
includes/api/ApiHelp.php
includes/api/ApiMain.php
includes/api/ApiParamInfo.php
includes/api/ApiQueryAllLinks.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryBacklinksprop.php
includes/api/ApiQueryFileRepoInfo.php
includes/api/ApiResult.php
includes/api/ApiUpload.php
includes/api/i18n/ja.json
includes/api/i18n/lt.json
includes/api/i18n/nap.json
includes/changes/ChangesList.php
includes/content/JsonContent.php
includes/db/DatabaseOracle.php
includes/db/loadbalancer/LoadMonitor.php
includes/filerepo/ForeignAPIRepo.php
includes/installer/MysqlUpdater.php
includes/installer/i18n/en.json
includes/installer/i18n/lt.json
includes/mail/EmailNotification.php
includes/parser/Parser.php
includes/search/SearchMssql.php
includes/session/BotPasswordSessionProvider.php
includes/session/Session.php
includes/session/SessionManager.php
includes/skins/SkinTemplate.php
includes/specials/SpecialBlock.php
includes/specials/SpecialUserlogin.php
includes/upload/UploadBase.php
includes/user/User.php
includes/utils/MWRestrictions.php
languages/classes/LanguageCu.php
languages/classes/LanguageHy.php
languages/classes/LanguageUk.php
languages/i18n/arq.json
languages/i18n/be-tarask.json
languages/i18n/bgn.json
languages/i18n/bn.json
languages/i18n/de.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/es.json
languages/i18n/fa.json
languages/i18n/fo.json
languages/i18n/fr.json
languages/i18n/frp.json
languages/i18n/gom-deva.json
languages/i18n/gom-latn.json
languages/i18n/it.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/mg.json
languages/i18n/pl.json
languages/i18n/qqq.json
languages/i18n/sl.json
languages/i18n/sv.json
languages/i18n/tr.json
languages/i18n/uk.json
maintenance/benchmarks/Benchmarker.php
maintenance/getConfiguration.php
maintenance/initSiteStats.php
tests/phpunit/includes/WatchedItemIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/WatchedItemStoreIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/WatchedItemStoreTest.php [deleted file]
tests/phpunit/includes/WatchedItemStoreUnitTest.php [new file with mode: 0644]
tests/phpunit/includes/WatchedItemUnitTest.php [new file with mode: 0644]
tests/phpunit/includes/api/RandomImageGenerator.php
tests/phpunit/includes/changes/OldChangesListTest.php
tests/phpunit/includes/media/WebPTest.php
tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
tests/phpunit/structure/ApiDocumentationTest.php
tests/qunit/data/generateJqueryMsgData.php

index 2e90a55..b64bf40 100644 (file)
@@ -327,6 +327,18 @@ changes to languages because of Phabricator reports.
   does not support categories.
 * wikidiff difference engine is no longer supported, anyone still using it are encouraged
   to upgrade to wikidiff2 which is actively maintained and has better package availability.
+* Database logic was removed from WatchedItem and a WatchedItemStore was created:
+** WatchedItem::IGNORE_USER_RIGHTS and WatchedItem::CHECK_USER_RIGHTS were deprecated.
+   User::IGNORE_USER_RIGHTS and User::CHECK_USER_RIGHTS were introduced.
+** WatchedItem::fromUserTitle was deprecated in favour of the constructor.
+** WatchedItem::resetNotificationTimestamp was deprecated.
+** WatchedItem::batchAddWatch was deprecated.
+** WatchedItem::addWatch was deprecated.
+** WatchedItem::removeWatch was deprecated.
+** WatchedItem::isWatched was deprecated.
+** WatchedItem::duplicateEntries was deprecated.
+** EmailNotification::updateWatchlistTimestamp was deprecated.
+** User::getWatchedItem was removed.
 
 == Compatibility ==
 
index c5f2424..a431f1b 100644 (file)
@@ -178,7 +178,8 @@ once for 'TimStarling', and once for 'brion'.
 
 Hooks can return three possible values:
 
-  * true: the hook has operated successfully
+  * No return value (or null): the hook has operated successfully. Previously,
+    true was required. This is the default since MediaWiki 1.23.
   * "some string": an error occurred; processing should stop and the error
                    should be shown to the user
   * false: the hook has successfully done the work necessary and the calling
index 482fcc6..3268700 100644 (file)
@@ -2109,7 +2109,7 @@ class EditPage {
                $watch = $this->watchthis;
                // Do this in its own transaction to reduce contention...
                DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) {
-                       if ( $watch == $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) ) {
+                       if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
                                return; // nothing to change
                        }
                        WatchAction::doWatchOrUnwatch( $watch, $title, $user );
index e48a399..3fa91fa 100644 (file)
@@ -1738,7 +1738,7 @@ function wfEscapeWikiText( $text ) {
                                $repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
                        }
                }
-               $repl2 = $repl2 ? '/\b(' . join( '|', $repl2 ) . '):/i' : '/^(?!)/';
+               $repl2 = $repl2 ? '/\b(' . implode( '|', $repl2 ) . '):/i' : '/^(?!)/';
        }
        $text = substr( strtr( "\n$text", $repl ), 1 );
        $text = preg_replace( $repl2, '$1&#58;', $text );
index afa4e1c..321b7e3 100644 (file)
@@ -369,7 +369,8 @@ class MovePage {
                $oldsnamespace = MWNamespace::getSubject( $this->oldTitle->getNamespace() );
                $newsnamespace = MWNamespace::getSubject( $this->newTitle->getNamespace() );
                if ( $oldsnamespace != $newsnamespace || $oldtitle != $newtitle ) {
-                       WatchedItem::duplicateEntries( $this->oldTitle, $this->newTitle );
+                       $store = WatchedItemStore::getDefaultInstance();
+                       $store->duplicateAllAssociatedEntries( $this->oldTitle, $this->newTitle );
                }
 
                Hooks::run(
index dfab03a..11c23f0 100644 (file)
@@ -2069,7 +2069,7 @@ class OutputPage extends ContextSource {
                foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
                        $this->addVaryHeader( $header, $options );
                }
-               return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) );
+               return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
        }
 
        /**
index c0ec97f..0ac3e46 100644 (file)
@@ -174,7 +174,7 @@ class Title implements LinkTarget {
                // make sure we are using the right one. To detect changes over the course
                // of a request, we remember a fingerprint of the config used to create the
                // codec singleton, and re-create it if the fingerprint doesn't match.
-               $fingerprint = spl_object_hash( $wgContLang ) . '|' . join( '+', $wgLocalInterwikis );
+               $fingerprint = spl_object_hash( $wgContLang ) . '|' . implode( '+', $wgLocalInterwikis );
 
                if ( $fingerprint !== $titleCodecFingerprint ) {
                        $titleCodec = null;
@@ -4466,8 +4466,12 @@ class Title implements LinkTarget {
                        $this->mNotificationTimestamp = [];
                }
 
-               $watchedItem = WatchedItem::fromUserTitle( $user, $this );
-               $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
+               $watchedItem = WatchedItemStore::getDefaultInstance()->getWatchedItem( $user, $this );
+               if ( $watchedItem ) {
+                       $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
+               } else {
+                       $this->mNotificationTimestamp[$uid] = false;
+               }
 
                return $this->mNotificationTimestamp[$uid];
        }
index b597f99..f2633d9 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 /**
- * Accessor and mutator for watchlist entries.
- *
  * 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
  * @file
  * @ingroup Watchlist
  */
+use Wikimedia\Assert\Assert;
 
 /**
  * Representation of a pair of user and title for watchlist entries.
  *
+ * @author Tim Starling
+ * @author Addshore
+ *
  * @ingroup Watchlist
  */
 class WatchedItem {
-       /** @var Title */
-       private $mTitle;
-
-       /** @var User */
-       private $mUser;
-
-       /** @var int */
-       private $mCheckRights;
-
-       /** @var bool */
-       private $loaded = false;
-
-       /** @var bool */
-       private $watched;
-
-       /** @var string */
-       private $timestamp;
 
        /**
-        * Constant to specify that user rights 'editmywatchlist' and
-        * 'viewmywatchlist' should not be checked.
-        * @since 1.22
+        * @deprecated since 1.27, see User::IGNORE_USER_RIGHTS
         */
-       const IGNORE_USER_RIGHTS = 0;
+       const IGNORE_USER_RIGHTS = User::IGNORE_USER_RIGHTS;
 
        /**
-        * Constant to specify that user rights 'editmywatchlist' and
-        * 'viewmywatchlist' should be checked.
-        * @since 1.22
+        * @deprecated since 1.27, see User::CHECK_USER_RIGHTS
         */
-       const CHECK_USER_RIGHTS = 1;
+       const CHECK_USER_RIGHTS = User::CHECK_USER_RIGHTS;
 
        /**
-        * Create a WatchedItem object with the given user and title
-        * @since 1.22 $checkRights parameter added
-        * @param User $user The user to use for (un)watching
-        * @param Title $title The title we're going to (un)watch
-        * @param int $checkRights Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights.
-        *     Pass either WatchedItem::IGNORE_USER_RIGHTS or WatchedItem::CHECK_USER_RIGHTS.
-        * @return WatchedItem
+        * @deprecated Internal class use only
         */
-       public static function fromUserTitle( $user, $title,
-               $checkRights = WatchedItem::CHECK_USER_RIGHTS
-       ) {
-               $wl = new WatchedItem;
-               $wl->mUser = $user;
-               $wl->mTitle = $title;
-               $wl->mCheckRights = $checkRights;
-
-               return $wl;
-       }
+       const DEPRECATED_USAGE_TIMESTAMP = -100;
 
        /**
-        * Title being watched
-        * @return Title
+        * @var bool
+        * @deprecated Internal class use only
         */
-       protected function getTitle() {
-               return $this->mTitle;
-       }
+       public $checkRights = User::CHECK_USER_RIGHTS;
 
        /**
-        * Helper to retrieve the title namespace
-        * @return int
+        * @var Title
+        * @deprecated Internal class use only
         */
-       protected function getTitleNs() {
-               return $this->getTitle()->getNamespace();
-       }
+       private $title;
 
        /**
-        * Helper to retrieve the title DBkey
-        * @return string
+        * @var LinkTarget
         */
-       protected function getTitleDBkey() {
-               return $this->getTitle()->getDBkey();
-       }
+       private $linkTarget;
 
        /**
-        * Helper to retrieve the user id
-        * @return int
+        * @var User
         */
-       protected function getUserId() {
-               return $this->mUser->getId();
-       }
+       private $user;
 
        /**
-        * Return an array of conditions to select or update the appropriate database
-        * row.
-        *
-        * @return array
+        * @var null|string the value of the wl_notificationtimestamp field
         */
-       private function dbCond() {
-               return [
-                       'wl_user' => $this->getUserId(),
-                       'wl_namespace' => $this->getTitleNs(),
-                       'wl_title' => $this->getTitleDBkey(),
-               ];
-       }
+       private $notificationTimestamp;
 
        /**
-        * Load the object from the database
+        * @param User $user
+        * @param LinkTarget $linkTarget
+        * @param null|string $notificationTimestamp the value of the wl_notificationtimestamp field
+        * @param bool|null $checkRights DO NOT USE - used internally for backward compatibility
         */
-       private function load() {
-               if ( $this->loaded ) {
-                       return;
-               }
-               $this->loaded = true;
-
-               // Only loggedin user can have a watchlist
-               if ( $this->mUser->isAnon() ) {
-                       $this->watched = false;
-                       return;
-               }
-
-               // some pages cannot be watched
-               if ( !$this->getTitle()->isWatchable() ) {
-                       $this->watched = false;
-                       return;
-               }
-
-               # Pages and their talk pages are considered equivalent for watching;
-               # remember that talk namespaces are numbered as page namespace+1.
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $row = $dbr->selectRow( 'watchlist', 'wl_notificationtimestamp',
-                       $this->dbCond(), __METHOD__ );
-
-               if ( $row === false ) {
-                       $this->watched = false;
-               } else {
-                       $this->watched = true;
-                       $this->timestamp = $row->wl_notificationtimestamp;
+       public function __construct(
+               User $user,
+               LinkTarget $linkTarget,
+               $notificationTimestamp,
+               $checkRights = null
+       ) {
+               $this->user = $user;
+               $this->linkTarget = $linkTarget;
+               $this->notificationTimestamp = $notificationTimestamp;
+               if ( $checkRights !== null ) {
+                       $this->checkRights = $checkRights;
                }
        }
 
        /**
-        * Check permissions
-        * @param string $what 'viewmywatchlist' or 'editmywatchlist'
-        * @return bool
+        * @return User
         */
-       private function isAllowed( $what ) {
-               return !$this->mCheckRights || $this->mUser->isAllowed( $what );
+       public function getUser() {
+               return $this->user;
        }
 
        /**
-        * Is mTitle being watched by mUser?
-        * @return bool
+        * @return LinkTarget
         */
-       public function isWatched() {
-               if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
-                       return false;
-               }
-
-               $this->load();
-               return $this->watched;
+       public function getLinkTarget() {
+               return $this->linkTarget;
        }
 
        /**
         * Get the notification timestamp of this entry.
         *
-        * @return bool|null|string False if the page is not watched, the value of
-        *   the wl_notificationtimestamp field otherwise
+        * @return bool|null|string
         */
        public function getNotificationTimestamp() {
-               if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
-                       return false;
-               }
-
-               $this->load();
-               if ( $this->watched ) {
-                       return $this->timestamp;
-               } else {
-                       return false;
+               // Back compat for objects constructed using self::fromUserTitle
+               if ( $this->notificationTimestamp === self::DEPRECATED_USAGE_TIMESTAMP ) {
+                       // wfDeprecated( __METHOD__, '1.27' );
+                       if ( $this->checkRights && !$this->user->isAllowed( 'viewmywatchlist' ) ) {
+                               return false;
+                       }
+                       $item = WatchedItemStore::getDefaultInstance()
+                               ->loadWatchedItem( $this->user, $this->linkTarget );
+                       if ( $item ) {
+                               $this->notificationTimestamp = $item->getNotificationTimestamp();
+                       } else {
+                               $this->notificationTimestamp = false;
+                       }
                }
+               return $this->notificationTimestamp;
        }
 
        /**
-        * Reset the notification timestamp of this entry
-        *
-        * @param bool $force Whether to force the write query to be executed even if the
-        *    page is not watched or the notification timestamp is already NULL.
-        * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
+        * Back compat pre 1.27 with the WatchedItemStore introduction
+        * @todo remove in 1.28/9
+        * -------------------------------------------------
         */
-       public function resetNotificationTimestamp(
-               $force = '', $oldid = 0
-       ) {
-               // Only loggedin user can have a watchlist
-               if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
-                       return;
-               }
 
-               if ( $force != 'force' ) {
-                       $this->load();
-                       if ( !$this->watched || $this->timestamp === null ) {
-                               return;
+       /**
+        * @return Title
+        * @deprecated Internal class use only
+        */
+       public function getTitle() {
+               if ( !$this->title ) {
+                       if ( $this->linkTarget instanceof Title ) {
+                               $this->title = $this->linkTarget;
+                       } else {
+                               $this->title = Title::newFromLinkTarget( $this->linkTarget );
                        }
                }
+               return $this->title;
+       }
 
-               $title = $this->getTitle();
-               if ( !$oldid ) {
-                       // No oldid given, assuming latest revision; clear the timestamp.
-                       $notificationTimestamp = null;
-               } elseif ( !$title->getNextRevisionID( $oldid ) ) {
-                       // Oldid given and is the latest revision for this title; clear the timestamp.
-                       $notificationTimestamp = null;
-               } else {
-                       // See if the version marked as read is more recent than the one we're viewing.
-                       // Call load() if it wasn't called before due to $force.
-                       $this->load();
-
-                       if ( $this->timestamp === null ) {
-                               // This can only happen if $force is enabled.
-                               $notificationTimestamp = null;
-                       } else {
-                               // Oldid given and isn't the latest; update the timestamp.
-                               // This will result in no further notification emails being sent!
-                               $notificationTimestamp = Revision::getTimestampFromId( $title, $oldid );
-                               // We need to go one second to the future because of various strict comparisons
-                               // throughout the codebase
-                               $ts = new MWTimestamp( $notificationTimestamp );
-                               $ts->timestamp->add( new DateInterval( 'PT1S' ) );
-                               $notificationTimestamp = $ts->getTimestamp( TS_MW );
+       /**
+        * @deprecated since 1.27 Use the constructor, WatchedItemStore::getWatchedItem()
+        *             or WatchedItemStore::loadWatchedItem()
+        */
+       public static function fromUserTitle( $user, $title, $checkRights = User::CHECK_USER_RIGHTS ) {
+               // wfDeprecated( __METHOD__, '1.27' );
+               return new self( $user, $title, self::DEPRECATED_USAGE_TIMESTAMP, (bool)$checkRights );
+       }
 
-                               if ( $notificationTimestamp < $this->timestamp ) {
-                                       if ( $force != 'force' ) {
-                                               return;
-                                       } else {
-                                               // This is a little silly…
-                                               $notificationTimestamp = $this->timestamp;
-                                       }
-                               }
-                       }
+       /**
+        * @deprecated since 1.27 Use WatchedItemStore::resetNotificationTimestamp()
+        */
+       public function resetNotificationTimestamp( $force = '', $oldid = 0 ) {
+               // wfDeprecated( __METHOD__, '1.27' );
+               if ( $this->checkRights && !$this->user->isAllowed( 'editmywatchlist' ) ) {
+                       return;
                }
-
-               // If the page is watched by the user (or may be watched), update the timestamp
-               $job = new ActivityUpdateJob(
-                       $title,
-                       [
-                               'type'      => 'updateWatchlistNotification',
-                               'userid'    => $this->getUserId(),
-                               'notifTime' => $notificationTimestamp,
-                               'curTime'   => time()
-                       ]
+               WatchedItemStore::getDefaultInstance()->resetNotificationTimestamp(
+                       $this->user,
+                       $this->getTitle(),
+                       $force,
+                       $oldid
                );
-               // Try to run this post-send
-               DeferredUpdates::addCallableUpdate( function() use ( $job ) {
-                       $job->run();
-               } );
-
-               $this->timestamp = null;
        }
 
        /**
-        * @param WatchedItem[] $items
-        * @return bool
+        * @deprecated since 1.27 Use WatchedItemStore::addWatchBatch()
         */
        public static function batchAddWatch( array $items ) {
-
-               if ( wfReadOnly() ) {
-                       return false;
-               }
-
-               $rows = [];
-               foreach ( $items as $item ) {
-                       // Only loggedin user can have a watchlist
-                       if ( $item->mUser->isAnon() || !$item->isAllowed( 'editmywatchlist' ) ) {
+               // wfDeprecated( __METHOD__, '1.27' );
+               $userTargetCombinations = [];
+               /** @var WatchedItem $watchedItem */
+               foreach ( $items as $watchedItem ) {
+                       if ( $watchedItem->checkRights && !$watchedItem->getUser()->isAllowed( 'editmywatchlist' ) ) {
                                continue;
                        }
-                       $rows[] = [
-                               'wl_user' => $item->getUserId(),
-                               'wl_namespace' => MWNamespace::getSubject( $item->getTitleNs() ),
-                               'wl_title' => $item->getTitleDBkey(),
-                               'wl_notificationtimestamp' => null,
+                       $userTargetCombinations[] = [
+                               $watchedItem->getUser(),
+                               $watchedItem->getTitle()->getSubjectPage()
                        ];
-                       // Every single watched page needs now to be listed in watchlist;
-                       // namespace:page and namespace_talk:page need separate entries:
-                       $rows[] = [
-                               'wl_user' => $item->getUserId(),
-                               'wl_namespace' => MWNamespace::getTalk( $item->getTitleNs() ),
-                               'wl_title' => $item->getTitleDBkey(),
-                               'wl_notificationtimestamp' => null
+                       $userTargetCombinations[] = [
+                               $watchedItem->getUser(),
+                               $watchedItem->getTitle()->getTalkPage()
                        ];
-                       $item->watched = true;
-               }
-
-               if ( !$rows ) {
-                       return false;
-               }
-
-               $dbw = wfGetDB( DB_MASTER );
-               foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
-                       // Use INSERT IGNORE to avoid overwriting the notification timestamp
-                       // if there's already an entry for this page
-                       $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' );
                }
-
-               return true;
+               $store = WatchedItemStore::getDefaultInstance();
+               return $store->addWatchBatch( $userTargetCombinations );
        }
 
        /**
-        * Given a title and user (assumes the object is setup), add the watch to the database.
+        * @deprecated since 1.27 Use User::addWatch()
         * @return bool
         */
        public function addWatch() {
-               return self::batchAddWatch( [ $this ] );
+               // wfDeprecated( __METHOD__, '1.27' );
+               $this->user->addWatch( $this->getTitle(), $this->checkRights );
+               return true;
        }
 
        /**
-        * Same as addWatch, only the opposite.
+        * @deprecated since 1.27 Use User::removeWatch()
         * @return bool
         */
        public function removeWatch() {
-
-               // Only loggedin user can have a watchlist
-               if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
+               // wfDeprecated( __METHOD__, '1.27' );
+               if ( $this->checkRights && !$this->user->isAllowed( 'editmywatchlist' ) ) {
                        return false;
                }
+               $this->user->removeWatch( $this->getTitle(), $this->checkRights );
+               return true;
+       }
 
-               $success = false;
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->delete( 'watchlist',
-                       [
-                               'wl_user' => $this->getUserId(),
-                               'wl_namespace' => MWNamespace::getSubject( $this->getTitleNs() ),
-                               'wl_title' => $this->getTitleDBkey(),
-                       ], __METHOD__
-               );
-               if ( $dbw->affectedRows() ) {
-                       $success = true;
-               }
-
-               # the following code compensates the new behavior, introduced by the
-               # enotif patch, that every single watched page needs now to be listed
-               # in watchlist namespace:page and namespace_talk:page had separate
-               # entries: clear them
-               $dbw->delete( 'watchlist',
-                       [
-                               'wl_user' => $this->getUserId(),
-                               'wl_namespace' => MWNamespace::getTalk( $this->getTitleNs() ),
-                               'wl_title' => $this->getTitleDBkey(),
-                       ], __METHOD__
-               );
-
-               if ( $dbw->affectedRows() ) {
-                       $success = true;
-               }
-
-               $this->watched = false;
-
-               return $success;
+       /**
+        * @deprecated since 1.27 Use User::isWatched()
+        * @return bool
+        */
+       public function isWatched() {
+               // wfDeprecated( __METHOD__, '1.27' );
+               return $this->user->isWatched( $this->getTitle(), $this->checkRights );
        }
 
        /**
-        * @deprecated since 1.27. See WatchedItemStore::duplicateEntry
-        *
-        * @param Title $oldTitle
-        * @param Title $newTitle
+        * @deprecated since 1.27 Use WatchedItemStore::duplicateAllAssociatedEntries()
         */
        public static function duplicateEntries( Title $oldTitle, Title $newTitle ) {
+               // wfDeprecated( __METHOD__, '1.27' );
                $store = WatchedItemStore::getDefaultInstance();
-               $store->duplicateEntry( $oldTitle->getSubjectPage(), $newTitle->getSubjectPage() );
-               $store->duplicateEntry( $oldTitle->getTalkPage(), $newTitle->getTalkPage() );
+               $store->duplicateAllAssociatedEntries( $oldTitle, $newTitle );
        }
 
 }
index 83a5856..1aed8e0 100644 (file)
@@ -1,8 +1,10 @@
 <?php
 
+use Wikimedia\Assert\Assert;
+
 /**
  * Storage layer class for WatchedItems.
- * Database interaction
+ * Database interaction.
  *
  * @author Addshore
  *
@@ -15,19 +17,465 @@ class WatchedItemStore {
         */
        private $loadBalancer;
 
-       public function __construct( LoadBalancer $loadBalancer ) {
+       /**
+        * @var BagOStuff
+        */
+       private $cache;
+
+       /**
+        * @var callable|null
+        */
+       private $deferredUpdatesAddCallableUpdateCallback;
+
+       /**
+        * @var callable|null
+        */
+       private $revisionGetTimestampFromIdCallback;
+
+       /**
+        * @var self|null
+        */
+       private static $instance;
+
+       public function __construct( LoadBalancer $loadBalancer, BagOStuff $cache ) {
                $this->loadBalancer = $loadBalancer;
+               $this->cache = $cache;
+               $this->deferredUpdatesAddCallableUpdateCallback = [ 'DeferredUpdates', 'addCallableUpdate' ];
+               $this->revisionGetTimestampFromIdCallback = [ 'Revision', 'getTimestampFromId' ];
+       }
+
+       /**
+        * Overrides the DeferredUpdates::addCallableUpdate callback
+        * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+        *
+        * @param callable $callback
+        * @see DeferredUpdates::addCallableUpdate for callback signiture
+        *
+        * @throws MWException
+        */
+       public function overrideDeferredUpdatesAddCallableUpdateCallback( $callback ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException(
+                               'Cannot override DeferredUpdates::addCallableUpdate callback in operation.'
+                       );
+               }
+               Assert::parameterType( 'callable', $callback, '$callback' );
+               $this->deferredUpdatesAddCallableUpdateCallback = $callback;
+       }
+
+       /**
+        * Overrides the Revision::getTimestampFromId callback
+        * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+        *
+        * @param callable $callback
+        * @see Revision::getTimestampFromId for callback signiture
+        *
+        * @throws MWException
+        */
+       public function overrideRevisionGetTimestampFromIdCallback( $callback ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException(
+                               'Cannot override Revision::getTimestampFromId callback in operation.'
+                       );
+               }
+               Assert::parameterType( 'callable', $callback, '$callback' );
+               $this->revisionGetTimestampFromIdCallback = $callback;
+       }
+
+       /**
+        * Overrides the default instance of this class
+        * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+        *
+        * @param WatchedItemStore $store
+        *
+        * @throws MWException
+        */
+       public static function overrideDefaultInstance( WatchedItemStore $store ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException(
+                               'Cannot override ' . __CLASS__ . 'default instance in operation.'
+                       );
+               }
+               self::$instance = $store;
        }
 
        /**
         * @return self
         */
        public static function getDefaultInstance() {
-               static $instance;
-               if ( !$instance ) {
-                       $instance = new self( wfGetLB() );
+               if ( !self::$instance ) {
+                       self::$instance = new self(
+                               wfGetLB(),
+                               new HashBagOStuff( [ 'maxKeys' => 100 ] )
+                       );
+               }
+               return self::$instance;
+       }
+
+       private function getCacheKey( User $user, LinkTarget $target ) {
+               return $this->cache->makeKey(
+                       (string)$target->getNamespace(),
+                       $target->getDBkey(),
+                       (string)$user->getId()
+               );
+       }
+
+       private function cache( WatchedItem $item ) {
+               $this->cache->set(
+                       $this->getCacheKey( $item->getUser(), $item->getLinkTarget() ),
+                       $item
+               );
+       }
+
+       private function uncache( User $user, LinkTarget $target ) {
+               $this->cache->delete( $this->getCacheKey( $user, $target ) );
+       }
+
+       /**
+        * @param User $user
+        * @param LinkTarget $target
+        *
+        * @return WatchedItem|null
+        */
+       private function getCached( User $user, LinkTarget $target ) {
+               return $this->cache->get( $this->getCacheKey( $user, $target ) );
+       }
+
+       /**
+        * Return an array of conditions to select or update the appropriate database
+        * row.
+        *
+        * @param User $user
+        * @param LinkTarget $target
+        *
+        * @return array
+        */
+       private function dbCond( User $user, LinkTarget $target ) {
+               return [
+                       'wl_user' => $user->getId(),
+                       'wl_namespace' => $target->getNamespace(),
+                       'wl_title' => $target->getDBkey(),
+               ];
+       }
+
+       /**
+        * Get an item (may be cached)
+        *
+        * @param User $user
+        * @param LinkTarget $target
+        *
+        * @return WatchedItem|false
+        */
+       public function getWatchedItem( User $user, LinkTarget $target ) {
+               $cached = $this->getCached( $user, $target );
+               if ( $cached ) {
+                       return $cached;
+               }
+               return $this->loadWatchedItem( $user, $target );
+       }
+
+       /**
+        * Loads an item from the db
+        *
+        * @param User $user
+        * @param LinkTarget $target
+        *
+        * @return WatchedItem|false
+        */
+       public function loadWatchedItem( User $user, LinkTarget $target ) {
+               // Only loggedin user can have a watchlist
+               if ( $user->isAnon() ) {
+                       return false;
+               }
+
+               $dbr = $this->loadBalancer->getConnection( DB_SLAVE, [ 'watchlist' ] );
+               $row = $dbr->selectRow(
+                       'watchlist',
+                       'wl_notificationtimestamp',
+                       $this->dbCond( $user, $target ),
+                       __METHOD__
+               );
+               $this->loadBalancer->reuseConnection( $dbr );
+
+               if ( !$row ) {
+                       return false;
+               }
+
+               $item = new WatchedItem(
+                       $user,
+                       $target,
+                       $row->wl_notificationtimestamp
+               );
+               $this->cache( $item );
+
+               return $item;
+       }
+
+       /**
+        * Must be called separately for Subject & Talk namespaces
+        *
+        * @param User $user
+        * @param LinkTarget $target
+        *
+        * @return bool
+        */
+       public function isWatched( User $user, LinkTarget $target ) {
+               return (bool)$this->getWatchedItem( $user, $target );
+       }
+
+       /**
+        * Must be called separately for Subject & Talk namespaces
+        *
+        * @param User $user
+        * @param LinkTarget $target
+        */
+       public function addWatch( User $user, LinkTarget $target ) {
+               $this->addWatchBatch( [ [ $user, $target ] ] );
+       }
+
+       /**
+        * @param array[] $userTargetCombinations array of arrays containing [0] => User [1] => LinkTarget
+        *
+        * @return bool success
+        */
+       public function addWatchBatch( array $userTargetCombinations ) {
+               if ( $this->loadBalancer->getReadOnlyReason() !== false ) {
+                       return false;
+               }
+
+               $rows = [];
+               foreach ( $userTargetCombinations as list( $user, $target ) ) {
+                       /**
+                        * @var User $user
+                        * @var LinkTarget $target
+                        */
+
+                       // Only loggedin user can have a watchlist
+                       if ( $user->isAnon() ) {
+                               continue;
+                       }
+                       $rows[] = [
+                               'wl_user' => $user->getId(),
+                               'wl_namespace' => $target->getNamespace(),
+                               'wl_title' => $target->getDBkey(),
+                               'wl_notificationtimestamp' => null,
+                       ];
+                       $this->uncache( $user, $target );
+               }
+
+               if ( !$rows ) {
+                       return false;
+               }
+
+               $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] );
+               foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
+                       // Use INSERT IGNORE to avoid overwriting the notification timestamp
+                       // if there's already an entry for this page
+                       $dbw->insert( 'watchlist', $toInsert, __METHOD__, 'IGNORE' );
+               }
+               $this->loadBalancer->reuseConnection( $dbw );
+
+               return true;
+       }
+
+       /**
+        * Removes the an entry for the User watching the LinkTarget
+        * Must be called separately for Subject & Talk namespaces
+        *
+        * @param User $user
+        * @param LinkTarget $target
+        *
+        * @return bool success
+        * @throws DBUnexpectedError
+        * @throws MWException
+        */
+       public function removeWatch( User $user, LinkTarget $target ) {
+               // Only logged in user can have a watchlist
+               if ( $this->loadBalancer->getReadOnlyReason() !== false || $user->isAnon() ) {
+                       return false;
                }
-               return $instance;
+
+               $this->uncache( $user, $target );
+
+               $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] );
+               $dbw->delete( 'watchlist',
+                       [
+                               'wl_user' => $user->getId(),
+                               'wl_namespace' => $target->getNamespace(),
+                               'wl_title' => $target->getDBkey(),
+                       ], __METHOD__
+               );
+               $success = (bool)$dbw->affectedRows();
+               $this->loadBalancer->reuseConnection( $dbw );
+
+               return $success;
+       }
+
+       /**
+        * @param User $editor The editor that triggered the update. Their notification
+        *  timestamp will not be updated(they have already seen it)
+        * @param LinkTarget $target The target to update timestamps for
+        * @param string $timestamp Set the update timestamp to this value
+        *
+        * @return int[] Array of user IDs the timestamp has been updated for
+        */
+       public function updateNotificationTimestamp( User $editor, LinkTarget $target, $timestamp ) {
+               $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] );
+               $res = $dbw->select( [ 'watchlist' ],
+                       [ 'wl_user' ],
+                       [
+                               'wl_user != ' . intval( $editor->getId() ),
+                               'wl_namespace' => $target->getNamespace(),
+                               'wl_title' => $target->getDBkey(),
+                               'wl_notificationtimestamp IS NULL',
+                       ], __METHOD__
+               );
+
+               $watchers = [];
+               foreach ( $res as $row ) {
+                       $watchers[] = intval( $row->wl_user );
+               }
+
+               if ( $watchers ) {
+                       // Update wl_notificationtimestamp for all watching users except the editor
+                       $fname = __METHOD__;
+                       $dbw->onTransactionIdle(
+                               function () use ( $dbw, $timestamp, $watchers, $target, $fname ) {
+                                       $dbw->update( 'watchlist',
+                                               [ /* SET */
+                                                       'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
+                                               ], [ /* WHERE */
+                                                       'wl_user' => $watchers,
+                                                       'wl_namespace' => $target->getNamespace(),
+                                                       'wl_title' => $target->getDBkey(),
+                                               ], $fname
+                                       );
+                               }
+                       );
+               }
+
+               $this->loadBalancer->reuseConnection( $dbw );
+
+               return $watchers;
+       }
+
+       /**
+        * Reset the notification timestamp of this entry
+        *
+        * @param User $user
+        * @param Title $title
+        * @param string $force Whether to force the write query to be executed even if the
+        *    page is not watched or the notification timestamp is already NULL.
+        *    'force' in order to force
+        * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
+        *
+        * @return bool success
+        */
+       public function resetNotificationTimestamp( User $user, Title $title, $force = '', $oldid = 0 ) {
+               // Only loggedin user can have a watchlist
+               if ( $this->loadBalancer->getReadOnlyReason() !== false || $user->isAnon() ) {
+                       return false;
+               }
+
+               $item = null;
+               if ( $force != 'force' ) {
+                       $item = $this->loadWatchedItem( $user, $title );
+                       if ( !$item || $item->getNotificationTimestamp() === null ) {
+                               return false;
+                       }
+               }
+
+               // If the page is watched by the user (or may be watched), update the timestamp
+               $job = new ActivityUpdateJob(
+                       $title,
+                       [
+                               'type'      => 'updateWatchlistNotification',
+                               'userid'    => $user->getId(),
+                               'notifTime' => $this->getNotificationTimestamp( $user, $title, $item, $force, $oldid ),
+                               'curTime'   => time()
+                       ]
+               );
+
+               // Try to run this post-send
+               // Calls DeferredUpdates::addCallableUpdate in normal operation
+               call_user_func(
+                       $this->deferredUpdatesAddCallableUpdateCallback,
+                       function() use ( $job ) {
+                               $job->run();
+                       }
+               );
+
+               $this->uncache( $user, $title );
+
+               return true;
+       }
+
+       private function getNotificationTimestamp( User $user, Title $title, $item, $force, $oldid ) {
+               if ( !$oldid ) {
+                       // No oldid given, assuming latest revision; clear the timestamp.
+                       return null;
+               }
+
+               if ( !$title->getNextRevisionID( $oldid ) ) {
+                       // Oldid given and is the latest revision for this title; clear the timestamp.
+                       return null;
+               }
+
+               if ( $item === null ) {
+                       $item = $this->loadWatchedItem( $user, $title );
+               }
+
+               if ( !$item ) {
+                       // This can only happen if $force is enabled.
+                       return null;
+               }
+
+               // Oldid given and isn't the latest; update the timestamp.
+               // This will result in no further notification emails being sent!
+               // Calls Revision::getTimestampFromId in normal operation
+               $notificationTimestamp = call_user_func(
+                       $this->revisionGetTimestampFromIdCallback,
+                       $title,
+                       $oldid
+               );
+
+               // We need to go one second to the future because of various strict comparisons
+               // throughout the codebase
+               $ts = new MWTimestamp( $notificationTimestamp );
+               $ts->timestamp->add( new DateInterval( 'PT1S' ) );
+               $notificationTimestamp = $ts->getTimestamp( TS_MW );
+
+               if ( $notificationTimestamp < $item->getNotificationTimestamp() ) {
+                       if ( $force != 'force' ) {
+                               return false;
+                       } else {
+                               // This is a little silly…
+                               return $item->getNotificationTimestamp();
+                       }
+               }
+
+               return $notificationTimestamp;
+       }
+
+       /**
+        * Check if the given title already is watched by the user, and if so
+        * add a watch for the new title.
+        *
+        * To be used for page renames and such.
+        *
+        * @param LinkTarget $oldTarget
+        * @param LinkTarget $newTarget
+        */
+       public function duplicateAllAssociatedEntries( LinkTarget $oldTarget, LinkTarget $newTarget ) {
+               if ( !$oldTarget instanceof Title ) {
+                       $oldTarget = Title::newFromLinkTarget( $oldTarget );
+               }
+               if ( !$newTarget instanceof Title ) {
+                       $newTarget = Title::newFromLinkTarget( $newTarget );
+               }
+
+               $this->duplicateEntry( $oldTarget->getSubjectPage(), $newTarget->getSubjectPage() );
+               $this->duplicateEntry( $oldTarget->getTalkPage(), $newTarget->getTalkPage() );
        }
 
        /**
index 6f1f3e8..5ec10e6 100644 (file)
@@ -682,7 +682,7 @@ class HistoryPager extends ReverseChronologicalPager {
                $s .= $dirmark;
 
                if ( $rev->isMinor() ) {
-                       $s .= ' ' . ChangesList::flag( 'minor' );
+                       $s .= ' ' . ChangesList::flag( 'minor', $this->getContext() );
                }
 
                # Sometimes rev_len isn't populated
index db8c82d..d002da8 100644 (file)
@@ -103,7 +103,7 @@ class RollbackAction extends FormlessAction {
                        ->parseAsBlock() );
 
                if ( $user->getBoolOption( 'watchrollback' ) ) {
-                       $user->addWatch( $this->page->getTitle(), WatchedItem::IGNORE_USER_RIGHTS );
+                       $user->addWatch( $this->page->getTitle(), User::IGNORE_USER_RIGHTS );
                }
 
                $this->getOutput()->returnToMain( false, $this->getTitle() );
index 8f13456..890740f 100644 (file)
@@ -82,12 +82,12 @@ class WatchAction extends FormAction {
         */
        public static function doWatchOrUnwatch( $watch, Title $title, User $user ) {
                if ( $user->isLoggedIn() &&
-                       $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) != $watch
+                       $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) != $watch
                ) {
                        // If the user doesn't have 'editmywatchlist', we still want to
                        // allow them to add but not remove items via edits and such.
                        if ( $watch ) {
-                               return self::doWatch( $title, $user, WatchedItem::IGNORE_USER_RIGHTS );
+                               return self::doWatch( $title, $user, User::IGNORE_USER_RIGHTS );
                        } else {
                                return self::doUnwatch( $title, $user );
                        }
@@ -101,15 +101,16 @@ class WatchAction extends FormAction {
         * @since 1.22 Returns Status, $checkRights parameter added
         * @param Title $title Page to watch/unwatch
         * @param User $user User who is watching/unwatching
-        * @param int $checkRights Passed through to $user->addWatch()
+        * @param bool $checkRights Passed through to $user->addWatch()
+        *     Pass User::CHECK_USER_RIGHTS or User::IGNORE_USER_RIGHTS.
         * @return Status
         */
-       public static function doWatch( Title $title, User $user,
-               $checkRights = WatchedItem::CHECK_USER_RIGHTS
+       public static function doWatch(
+               Title $title,
+               User $user,
+               $checkRights = User::CHECK_USER_RIGHTS
        ) {
-               if ( $checkRights !== WatchedItem::IGNORE_USER_RIGHTS &&
-                       !$user->isAllowed( 'editmywatchlist' )
-               ) {
+               if ( $checkRights && !$user->isAllowed( 'editmywatchlist' ) ) {
                        return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
                }
 
index b55ad03..85dee2b 100644 (file)
@@ -302,7 +302,7 @@ abstract class ApiBase extends ContextSource {
                                        $qs = $k;
                                        $msg = self::escapeWikiText( $v );
                                        if ( is_array( $msg ) ) {
-                                               $msg = join( ' ', $msg );
+                                               $msg = implode( ' ', $msg );
                                        }
                                }
 
@@ -547,13 +547,13 @@ abstract class ApiBase extends ContextSource {
                        $parent = $module;
                        $manager = $parent->getModuleManager();
                        if ( $manager === null ) {
-                               $errorPath = join( '+', array_slice( $parts, 0, $i ) );
+                               $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
                                $this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' );
                        }
                        $module = $manager->getModule( $parts[$i] );
 
                        if ( $module === null ) {
-                               $errorPath = $i ? join( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
+                               $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
                                $this->dieUsage(
                                        "The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"",
                                        'badmodule'
@@ -830,7 +830,7 @@ abstract class ApiBase extends ContextSource {
         */
        protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
 
-               $userWatching = $this->getUser()->isWatched( $titleObj, WatchedItem::IGNORE_USER_RIGHTS );
+               $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
 
                switch ( $watchlist ) {
                        case 'watch':
@@ -2223,7 +2223,7 @@ abstract class ApiBase extends ContextSource {
                Hooks::run( 'APIGetDescription', [ &$this, &$desc ] );
                $desc = self::escapeWikiText( $desc );
                if ( is_array( $desc ) ) {
-                       $desc = join( "\n", $desc );
+                       $desc = implode( "\n", $desc );
                } else {
                        $desc = (string)$desc;
                }
@@ -2309,7 +2309,7 @@ abstract class ApiBase extends ContextSource {
                                        }
                                        return $line;
                                }, $d );
-                               $d = join( ' ', $d );
+                               $d = implode( ' ', $d );
                        }
 
                        if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
index 25407bf..8f1bd19 100644 (file)
@@ -137,7 +137,7 @@ class ApiContinuationManager {
                }
                $paramName = $module->encodeParamName( $paramName );
                if ( is_array( $paramValue ) ) {
-                       $paramValue = join( '|', $paramValue );
+                       $paramValue = implode( '|', $paramValue );
                }
                $this->continuationData[$name][$paramName] = $paramValue;
        }
@@ -152,7 +152,7 @@ class ApiContinuationManager {
                $name = $module->getModuleName();
                $paramName = $module->encodeParamName( $paramName );
                if ( is_array( $paramValue ) ) {
-                       $paramValue = join( '|', $paramValue );
+                       $paramValue = implode( '|', $paramValue );
                }
                $this->generatorContinuationData[$name][$paramName] = $paramValue;
        }
@@ -193,7 +193,7 @@ class ApiContinuationManager {
                                $data += $kvp;
                        }
                        $data += $this->generatorParams;
-                       $generatorKeys = join( '|', array_keys( $this->generatorParams ) );
+                       $generatorKeys = implode( '|', array_keys( $this->generatorParams ) );
                } elseif ( $this->generatorContinuationData ) {
                        // All the generator-using modules are complete, but the
                        // generator isn't. Continue the generator and restart the
@@ -204,7 +204,7 @@ class ApiContinuationManager {
                        }
                        $data += $generatorParams;
                        $finishedModules = array_diff( $finishedModules, $this->generatedModules );
-                       $generatorKeys = join( '|', array_keys( $generatorParams ) );
+                       $generatorKeys = implode( '|', array_keys( $generatorParams ) );
                        $batchcomplete = true;
                } else {
                        // Generator and prop modules are all done. Mark it so.
@@ -215,7 +215,7 @@ class ApiContinuationManager {
                // Set 'continue' if any continuation data is set or if the generator
                // still needs to run
                if ( $data || $generatorKeys !== '-' ) {
-                       $data['continue'] = $generatorKeys . '||' . join( '|', $finishedModules );
+                       $data['continue'] = $generatorKeys . '||' . implode( '|', $finishedModules );
                }
 
                return [ $data, $batchcomplete ];
index 349a34d..f2d6329 100644 (file)
@@ -268,7 +268,7 @@ class ApiHelp extends ApiBase {
                                        'level' => $level,
                                        'anchor' => $anchor,
                                        'line' => $header,
-                                       'number' => join( '.', $tocnumber ),
+                                       'number' => implode( '.', $tocnumber ),
                                        'index' => false,
                                ];
                                if ( empty( $options['noheader'] ) ) {
@@ -618,7 +618,7 @@ class ApiHelp extends ApiBase {
                                                                        ->parse();
                                                        }
                                                        if ( $extra ) {
-                                                               $info[] = join( ' ', $extra );
+                                                               $info[] = implode( ' ', $extra );
                                                        }
                                                }
                                        }
@@ -655,7 +655,7 @@ class ApiHelp extends ApiBase {
                                        }
 
                                        if ( $description ) {
-                                               $description = join( '', $description );
+                                               $description = implode( '', $description );
                                                $description = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $description );
                                                $help['parameters'] .= Html::rawElement( 'dd',
                                                        [ 'class' => 'description' ], $description );
@@ -744,7 +744,7 @@ class ApiHelp extends ApiBase {
 
                        Hooks::run( 'APIHelpModifyOutput', [ $module, &$help, $suboptions, &$haveModules ] );
 
-                       $out .= join( "\n", $help );
+                       $out .= implode( "\n", $help );
                }
 
                return $out;
index d59151a..f09c6f2 100644 (file)
@@ -1225,7 +1225,7 @@ class ApiMain extends ApiBase {
                // If a majority of slaves are too lagged then disallow writes
                $slaveCount = wfGetLB()->getServerCount() - 1;
                if ( $numLagged >= ceil( $slaveCount / 2 ) ) {
-                       $laggedServers = join( ', ', $laggedServers );
+                       $laggedServers = implode( ', ', $laggedServers );
                        wfDebugLog(
                                'api-readonly',
                                "Api request failed as read only because the following DBs are lagged: $laggedServers"
@@ -1443,7 +1443,7 @@ class ApiMain extends ApiBase {
                $ret = $this->getRequest()->getVal( $name );
                if ( $ret === null ) {
                        if ( $this->getRequest()->getArray( $name ) !== null ) {
-                               // See bug 10262 for why we don't just join( '|', ... ) the
+                               // See bug 10262 for why we don't just implode( '|', ... ) the
                                // array.
                                $this->setWarning(
                                        "Parameter '$name' uses unsupported PHP array syntax"
@@ -1637,7 +1637,7 @@ class ApiMain extends ApiBase {
                                        'level' => $level,
                                        'anchor' => 'main/datatypes',
                                        'line' => $header,
-                                       'number' => join( '.', $tocnumber ),
+                                       'number' => implode( '.', $tocnumber ),
                                        'index' => false,
                                ];
                        }
@@ -1656,7 +1656,7 @@ class ApiMain extends ApiBase {
                                        'level' => $level,
                                        'anchor' => 'main/credits',
                                        'line' => $header,
-                                       'number' => join( '.', $tocnumber ),
+                                       'number' => implode( '.', $tocnumber ),
                                        'index' => false,
                                ];
                        }
index 6e44f82..c3c9e21 100644 (file)
@@ -137,7 +137,7 @@ class ApiParamInfo extends ApiBase {
                                foreach ( $msgs as $m ) {
                                        $ret[] = $m->setContext( $this->context )->text();
                                }
-                               $res[$key] = join( "\n\n", $ret );
+                               $res[$key] = implode( "\n\n", $ret );
                                if ( $joinLists ) {
                                        $res[$key] = preg_replace( '!^(([*#:;])[^\n]*)\n\n(?=\2)!m', "$1\n", $res[$key] );
                                }
@@ -148,7 +148,7 @@ class ApiParamInfo extends ApiBase {
                                foreach ( $msgs as $m ) {
                                        $ret[] = $m->setContext( $this->context )->parseAsBlock();
                                }
-                               $ret = join( "\n", $ret );
+                               $ret = implode( "\n", $ret );
                                if ( $joinLists ) {
                                        $ret = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $ret );
                                }
index 94707da..ac90605 100644 (file)
@@ -117,7 +117,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
                        if ( $matches ) {
                                $p = $this->getModulePrefix();
                                $this->dieUsage(
-                                       "Cannot use {$p}prop=" . join( '|', array_keys( $matches ) ) . " with {$p}unique",
+                                       "Cannot use {$p}prop=" . implode( '|', array_keys( $matches ) ) . " with {$p}unique",
                                        'params'
                                );
                        }
index 97b122a..fb502e4 100644 (file)
@@ -296,7 +296,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                                // Note we must keep the parameters for the first query constant
                                // This may be overridden at a later step
                                $title = $row->{$this->bl_title};
-                               $this->continueStr = join( '|', array_slice( $this->cont, 0, 2 ) ) .
+                               $this->continueStr = implode( '|', array_slice( $this->cont, 0, 2 ) ) .
                                        "|$ns|$title|{$row->from_ns}|{$row->page_id}";
                                break;
                        }
@@ -451,7 +451,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                                                [ 'query', $this->getModuleName() ],
                                                $idx, array_diff_key( $arr, [ 'redirlinks' => '' ] ) );
                                        if ( !$fit ) {
-                                               $this->continueStr = join( '|', array_slice( $this->cont, 0, 6 ) ) .
+                                               $this->continueStr = implode( '|', array_slice( $this->cont, 0, 6 ) ) .
                                                        "|$pageID";
                                                break;
                                        }
@@ -474,7 +474,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                                                        [ 'query', $this->getModuleName(), $idx, 'redirlinks' ],
                                                        null, $redir );
                                                if ( !$fit ) {
-                                                       $this->continueStr = join( '|', array_slice( $this->cont, 0, 6 ) ) .
+                                                       $this->continueStr = implode( '|', array_slice( $this->cont, 0, 6 ) ) .
                                                                "|$pageID|$key";
                                                        break;
                                                }
index 5b55332..3810e90 100644 (file)
@@ -313,7 +313,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
                foreach ( $sortby as $field => $v ) {
                        $cont[] = $row->$field;
                }
-               $this->setContinueEnumParameter( 'continue', join( '|', $cont ) );
+               $this->setContinueEnumParameter( 'continue', implode( '|', $cont ) );
        }
 
        public function getCacheMode( $params ) {
index 7848bc8..c491236 100644 (file)
@@ -78,7 +78,7 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
 
                return [
                        'prop' => [
-                               ApiBase::PARAM_DFLT => join( '|', $props ),
+                               ApiBase::PARAM_DFLT => implode( '|', $props ),
                                ApiBase::PARAM_ISMULTI => true,
                                ApiBase::PARAM_TYPE => $props,
                        ],
index a5f22c3..3436320 100644 (file)
@@ -312,7 +312,7 @@ class ApiResult implements ApiSerializable {
                        if ( !$conflicts ) {
                                $arr[$name] += $value;
                        } else {
-                               $keys = join( ', ', array_keys( $conflicts ) );
+                               $keys = implode( ', ', array_keys( $conflicts ) );
                                throw new RuntimeException(
                                        "Conflicting keys ($keys) when attempting to merge element $name"
                                );
@@ -1132,12 +1132,12 @@ class ApiResult implements ApiSerializable {
                                                $tmp = [];
                                                return $tmp;
                                        default:
-                                               $fail = join( '.', array_slice( $path, 0, $i + 1 ) );
+                                               $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
                                                throw new InvalidArgumentException( "Path $fail does not exist" );
                                }
                        }
                        if ( !is_array( $ret[$k] ) ) {
-                               $fail = join( '.', array_slice( $path, 0, $i + 1 ) );
+                               $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
                                throw new InvalidArgumentException( "Path $fail is not an array" );
                        }
                        $ret = &$ret[$k];
index 0eecaa1..326f8ba 100644 (file)
@@ -544,7 +544,7 @@ class ApiUpload extends ApiBase {
 
                                $msg = 'Filetype not permitted: ';
                                if ( isset( $verification['blacklistedExt'] ) ) {
-                                       $msg .= join( ', ', $verification['blacklistedExt'] );
+                                       $msg .= implode( ', ', $verification['blacklistedExt'] );
                                        $extradata['blacklisted'] = array_values( $verification['blacklistedExt'] );
                                        ApiResult::setIndexedTagName( $extradata['blacklisted'], 'ext' );
                                } else {
index 08d087a..04a7554 100644 (file)
        "apihelp-paraminfo-param-querymodules": "クエリモジュール名のリスト (<var>prop</var>, <var>meta</var> or <var>list</var> パラメータの値)。<kbd>$1querymodules=foo</kbd> の代わりに <kbd>$1modules=query+foo</kbd> を使用してください。",
        "apihelp-paraminfo-example-1": "<kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, and <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> に関する情報を表示する。",
        "apihelp-parse-param-summary": "構文解析のための要約",
+       "apihelp-parse-param-page": "このページの内容を構文解析します。<var>$1text</var> および <var>$1title</var> とは同時に使用できません。",
        "apihelp-parse-param-redirects": "もし <var>$1page</var> や <var>$1pageid</var> に転送ページが指定された場合、それを解決する。",
        "apihelp-parse-param-prop": "どの情報を取得するか:",
        "apihelp-parse-paramvalue-prop-text": "ウィキテキストの解析されたテキストを提供します。",
index 5ad108b..da676a1 100644 (file)
@@ -4,6 +4,13 @@
                        "Zygimantus"
                ]
        },
+       "apihelp-createaccount-param-name": "Naudotojo vardas.",
+       "apihelp-createaccount-param-realname": "Vardas (nebūtina).",
+       "apihelp-delete-description": "Ištrinti puslapį.",
+       "apihelp-edit-param-text": "Puslapio turinys.",
+       "apihelp-emailuser-description": "Siųsti el. laišką naudotojui.",
+       "apihelp-expandtemplates-param-title": "Puslapio pavadinimas.",
+       "apihelp-feedrecentchanges-example-simple": "Parodyti naujausius keitimus.",
        "apihelp-query+alldeletedrevisions-example-user": "Sąrašas paskutinių 50 ištrintų indėlių pagal vartotoją\n<kbd>Pavyzdys</kbd>.",
        "apihelp-query+allrevisions-param-namespace": "Rodyti puslapius tik šioje vardų srityje.",
        "apihelp-query+backlinks-example-simple": "Rodyti nuorodas <kbd>Pagrindinis puslapis</kbd>.",
index b5cba60..a43d840 100644 (file)
        "apihelp-edit-param-title": "Titolo d' 'a paggena a cagnà. Nun se pò ausà nziem'a <var>$1pageid</var>.",
        "apihelp-edit-param-pageid": "ID d' 'a paggena a cagnà. Nun se pò ausà nziem'a <var>$1title</var>.",
        "apihelp-edit-param-section": "Nummero 'e sezione. <kbd>0</kbd> p' 'a sezione ncoppa, <kbd>new</kbd> pe' na seziona nova.",
+       "apihelp-edit-param-sectiontitle": "'O titolo pe' na seziona nova.",
        "apihelp-edit-param-text": "Cuntenuto 'e paggena.",
+       "apihelp-edit-param-summary": "Oggetto d' 'a modifica. Pure 'o titolo ra sezione quanno $1sezione=new e $1sectiontitle nun è mpustato.",
+       "apihelp-edit-param-tags": "Cagna 'e tag ca s'avesser'applicà 'a verziona.",
+       "apihelp-edit-param-minor": "Cagnamiento piccerillo.",
+       "apihelp-edit-param-notminor": "Cagnamiento nun-piccerillo.",
+       "apihelp-edit-param-bot": "Nzegna stu cagnamiento comm' 'e bot.",
+       "apihelp-edit-param-basetimestamp": "Nzegna 'o tiempo d' 'a verzione bbase, ausato pe' puté ffà scummiglià cunflitte 'edizione. Se putesse piglià pe' bbìa 'e [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
+       "apihelp-edit-param-starttimestamp": "Nzegna 'o tiempo d' 'a verzione bbase, ausato pe' puté ffà scummiglià cunflitte 'edizione. Nu valore buono se putess'arrepiglià pe' bbìa 'e <var>[[Special:ApiHelp/main|curtimestamp]]</var> quann'accummencia 'o prucess' 'edizione (e.g. quanno se stà a carrecà 'o contenuto 'e na paggena p' 'a cagnà).",
+       "apihelp-edit-param-recreate": "Scrive ncopp'a cocch'errore ncopp'a paggena avenno scancellato chesto a nu certo punto.",
+       "apihelp-edit-param-createonly": "Nun cagnà 'a paggena si esiste già.",
+       "apihelp-edit-param-nocreate": "Ietta 'errore si 'a paggena nun esiste.",
+       "apihelp-edit-param-watch": "Azzecc' 'a paggena â lista 'e paggene cuntrullate.",
+       "apihelp-edit-param-unwatch": "Liev' 'a paggena â lista 'e paggene cuntrullate.",
+       "apihelp-edit-param-watchlist": "Senza condizione, azzeccà o luvà 'a paggena 'a l'elenco 'e paggene cuntrullate 'e ll'utente, ausà mpustaziune o nun 'o cagnà l'elenco.",
+       "apihelp-edit-param-md5": "'O hash MD5 d' 'o parammetro 'e $1text, o chill' 'e $1prependtext e $1appendtext concatenate. Si mpustato, 'o cagnamiento nun fosse fatto... 'o cuntrario succeresse si 'o hash fosse curretto.",
        "apihelp-edit-param-prependtext": "Azzecca stu testo addò 'o cap' 'e paggena. Se mettesse ncuoll'a $1text.",
        "apihelp-edit-param-appendtext": "Azzecca stu testo addò 'o cap' 'e paggena. Se mettesse ncuoll'a $1text.\n\nAusate $1section=new pe' ne puté appennere na seziona nova, ato che ausà stu parammetro.",
        "apihelp-edit-param-undo": "Torna arrèto sta verziona. Miette ncuollo 'o $1text, $1prependtext e $1appendtext.",
        "apihelp-expandtemplates-paramvalue-prop-properties": "'E pruprietà 'e pagena definite p' 'e parole magiche spannute dint' 'o wikitesto.",
        "apihelp-expandtemplates-paramvalue-prop-volatile": "Si l'output fosse volatile e nun s'avess'ausà n'atavota addò servesse dint' 'a paggena.",
        "apihelp-expandtemplates-paramvalue-prop-ttl": "'O tiempo massimo aropp' 'o quale 'e caches d' 'o risultato s'avessero a nzegnà invalide.",
+       "apihelp-expandtemplates-paramvalue-prop-modules": "Ogne modulo ResourceLoader ch' 'e funzione parser addimannajero a s'azzeccà a ll'output. Fosse <kbd>jsconfigvars</kbd> o pure <kbd>encodedjsconfigvars</kbd> s'avesser'addimannà tutte 'nzieme pe' bbìa d' 'e <kbd>modules</kbd>.",
+       "apihelp-expandtemplates-paramvalue-prop-jsconfigvars": "Dà nfurmaziune 'e variabbele 'e mpustaziona JavaScript specifiche 'a paggena.",
+       "apihelp-expandtemplates-paramvalue-prop-encodedjsconfigvars": "Dà 'e variabbele 'e mpustaziona 'e JavaScript specifiche 'a na paggena comm'a na stringa JSON.",
+       "apihelp-expandtemplates-paramvalue-prop-parsetree": "L'albero 'e parse XML 'a ll'input.",
+       "apihelp-expandtemplates-param-includecomments": "Si s'avess'azzeccà cocche cummento HTML dint'a ll'output.",
+       "apihelp-expandtemplates-param-generatexml": "Generà ll'albero XML (scagnato 'a $1prop=parsetree).",
+       "apihelp-expandtemplates-example-simple": "Spanne 'o wikitesto <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>.",
+       "apihelp-feedcontributions-description": "Tuorna nu feed 'e cuntribbute 'utente.",
+       "apihelp-feedcontributions-param-feedformat": "'O furmatto d' 'o feed.",
+       "apihelp-feedcontributions-param-user": "'A quale 'utente nc'avimm'a piglià cuntribbute.",
+       "apihelp-feedcontributions-param-namespace": "'A qualu namespace s'avesser'a filtrà 'e cuntribbute.",
+       "apihelp-feedcontributions-param-year": "'E ll'anno (e primma).",
+       "apihelp-feedcontributions-param-month": "D' 'o mese (e pure cchiù primma).",
        "apihelp-feedwatchlist-param-feedformat": "'O furmato d' 'o feed.",
        "apihelp-login-example-login": "Tràse.",
        "apihelp-move-description": "Mòve paggena.",
index 15432da..637eb88 100644 (file)
@@ -132,7 +132,7 @@ class ChangesList extends ContextSource {
                $f = '';
                foreach ( array_keys( $this->getConfig()->get( 'RecentChangesFlags' ) ) as $flag ) {
                        $f .= isset( $flags[$flag] ) && $flags[$flag]
-                               ? self::flag( $flag )
+                               ? self::flag( $flag, $this->getContext() )
                                : $nothing;
                }
 
@@ -168,40 +168,40 @@ class ChangesList extends ContextSource {
        }
 
        /**
-        * Provide the "<abbr>" element appropriate to a given abbreviated flag,
-        * namely the flag indicating a new page, a minor edit, a bot edit, or an
-        * unpatrolled edit.  By default in English it will contain "N", "m", "b",
-        * "!" respectively, plus it will have an appropriate title and class.
+        * Make an "<abbr>" element for a given change flag. The flag indicating a new page, minor edit,
+        * bot edit, or unpatrolled edit. In English it typically contains "N", "m", "b", or "!".
         *
         * @param string $flag One key of $wgRecentChangesFlags
-        * @return string Raw HTML
+        * @param IContextSource $context
+        * @return string HTML
         */
-       public static function flag( $flag ) {
+       public static function flag( $flag, IContextSource $context = null ) {
+               static $map = [ 'minoredit' => 'minor', 'botedit' => 'bot' ];
                static $flagInfos = null;
+
                if ( is_null( $flagInfos ) ) {
                        global $wgRecentChangesFlags;
                        $flagInfos = [];
                        foreach ( $wgRecentChangesFlags as $key => $value ) {
-                               $flagInfos[$key]['letter'] = wfMessage( $value['letter'] )->escaped();
-                               $flagInfos[$key]['title'] = wfMessage( $value['title'] )->escaped();
+                               $flagInfos[$key]['letter'] = $value['letter'];
+                               $flagInfos[$key]['title'] = $value['title'];
                                // Allow customized class name, fall back to flag name
-                               $flagInfos[$key]['class'] = Sanitizer::escapeClass(
-                                       isset( $value['class'] ) ? $value['class'] : $key );
+                               $flagInfos[$key]['class'] = isset( $value['class'] ) ? $value['class'] : $key;
                        }
                }
 
-               // Inconsistent naming, bleh, kepted for b/c
-               $map = [
-                       'minoredit' => 'minor',
-                       'botedit' => 'bot',
-               ];
+               $context = $context ?: RequestContext::getMain();
+
+               // Inconsistent naming, kepted for b/c
                if ( isset( $map[$flag] ) ) {
                        $flag = $map[$flag];
                }
 
-               return "<abbr class='" . $flagInfos[$flag]['class'] . "' title='" .
-                       $flagInfos[$flag]['title'] . "'>" . $flagInfos[$flag]['letter'] .
-                       '</abbr>';
+               $info = $flagInfos[$flag];
+               return Html::element( 'abbr', [
+                       'class' => $info['class'],
+                       'title' => wfMessage( $info['title'] )->setContext( $context )->text(),
+               ], wfMessage( $info['letter'] )->setContext( $context )->text() );
        }
 
        /**
@@ -337,7 +337,7 @@ class ChangesList extends ContextSource {
         */
        public function insertLog( &$s, $title, $logtype ) {
                $page = new LogPage( $logtype );
-               $logname = $page->getName()->escaped();
+               $logname = $page->getName()->setContext( $this->getContext() )->escaped();
                $s .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $title, $logname ) )->escaped();
        }
 
index b9215fc..40d9277 100644 (file)
@@ -161,7 +161,7 @@ class JsonContent extends TextContent {
                        );
                }
                return Html::rawElement( 'table', [ 'class' => 'mw-json' ],
-                       Html::rawElement( 'tbody', [], join( '', $rows ) )
+                       Html::rawElement( 'tbody', [], implode( '', $rows ) )
                );
        }
 
@@ -200,7 +200,7 @@ class JsonContent extends TextContent {
                        );
                }
                return Html::rawElement( 'table', [ 'class' => 'mw-json' ],
-                       Html::rawElement( 'tbody', [], join( "\n", $rows ) )
+                       Html::rawElement( 'tbody', [], implode( "\n", $rows ) )
                );
        }
 
index 9b301a9..9e53653 100644 (file)
@@ -618,7 +618,7 @@ class DatabaseOracle extends Database {
 
                $table = $this->tableName( $table );
                // "INSERT INTO tables (a, b, c)"
-               $sql = "INSERT INTO " . $table . " (" . join( ',', array_keys( $row ) ) . ')';
+               $sql = "INSERT INTO " . $table . " (" . implode( ',', array_keys( $row ) ) . ')';
                $sql .= " VALUES (";
 
                // for each value, append ":key"
index d5cd017..e68cf1a 100644 (file)
@@ -36,7 +36,7 @@ interface LoadMonitor {
 
        /**
         * Perform pre-connection load ratio adjustment.
-        * @param array $loads
+        * @param array &$loads
         * @param string|bool $group The selected query group. Default: false
         * @param string|bool $wiki Default: false
         */
index d29cd7d..cc9099c 100644 (file)
@@ -537,7 +537,7 @@ class ForeignAPIRepo extends FileRepo {
         * @since 1.23
         */
        protected static function getIIProps() {
-               return join( '|', self::$imageInfoProps );
+               return implode( '|', self::$imageInfoProps );
        }
 
        /**
index 57eaffe..154f7c3 100644 (file)
@@ -516,7 +516,7 @@ class MysqlUpdater extends DatabaseUpdater {
                                $prev_title = $row->cur_title;
                                $prev_namespace = $row->cur_namespace;
                        }
-                       $sql = "DELETE FROM $cur WHERE cur_id IN ( " . join( ',', $deleteId ) . ')';
+                       $sql = "DELETE FROM $cur WHERE cur_id IN ( " . implode( ',', $deleteId ) . ')';
                        $this->db->query( $sql, __METHOD__ );
                        $this->output( wfTimestamp( TS_DB ) );
                        $this->output( "......<b>Deleted</b> " . $this->db->affectedRows() . " records.\n" );
index 865447a..6fa5930 100644 (file)
        "config-install-mainpage": "Creating main page with default content",
        "config-install-extension-tables": "Creating tables for enabled extensions",
        "config-install-mainpage-failed": "Could not insert main page: $1",
-       "config-install-done": "<strong>Congratulations!</strong>\nYou have successfully installed MediaWiki.\n\nThe installer has generated a <code>LocalSettings.php</code> file.\nIt contains all your configuration.\n\nYou will need to download it and put it in the base of your wiki installation (the same directory as index.php). The download should have started automatically.\n\nIf the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:\n\n$3\n\n<strong>Note:</strong> If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.\n\nWhen that has been done, you can <strong>[$2 enter your wiki]</strong>.",
+       "config-install-done": "<strong>Congratulations!</strong>\nYou have installed MediaWiki.\n\nThe installer has generated a <code>LocalSettings.php</code> file.\nIt contains all your configuration.\n\nYou will need to download it and put it in the base of your wiki installation (the same directory as index.php). The download should have started automatically.\n\nIf the download was not offered, or if you cancelled it, you can restart the download by clicking the link below:\n\n$3\n\n<strong>Note:</strong> If you do not do this now, this generated configuration file will not be available to you later if you exit the installation without downloading it.\n\nWhen that has been done, you can <strong>[$2 enter your wiki]</strong>.",
        "config-download-localsettings": "Download <code>LocalSettings.php</code>",
        "config-help": "help",
        "config-help-tooltip": "click to expand",
        "config-nofile": "File \"$1\" could not be found. Has it been deleted?",
        "config-extension-link": "Did you know that your wiki supports [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions]?\n\nYou can browse [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions by category] or the [//www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] to see the full list of extensions.",
-       "mainpagetext": "<strong>MediaWiki has been successfully installed.</strong>",
+       "mainpagetext": "<strong>MediaWiki has been installed.</strong>",
        "mainpagedocfooter": "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
 }
index 71c5231..e7bed0e 100644 (file)
@@ -13,7 +13,7 @@
        "config-localsettings-upgrade": "Aptiktas failas <code>LocalSettings.php</code>. Norėdami patobulinti šią instaliaciją, prašome įvesti reikšmę <code>$wgUpgradeKey</code> į dėžutę žemiau. Jūs rasite ją <code>LocalSettings.php</code>.",
        "config-localsettings-cli-upgrade": "Aptiktas failas <code>LocalSettings.php</code>. Tam kad patobulinti šią instaliaciją, prašome paleisti <code>update.php</code>.",
        "config-localsettings-key": "Naujinimo raktas:",
-       "config-localsettings-badkey": "Raktas, kurį pateikėte, yra neteisingas.",
+       "config-localsettings-badkey": "Atnaujinimo raktas, kurį pateikėte, yra neteisingas.",
        "config-upgrade-key-missing": "Aptikta esama MediaWiki instaliacija. Tam kad atnaujinti šią instaliaciją, prašome įrašyti šią eilutę <code>LocalSettings.php</code> failo pabaigoje:\n\n$1",
        "config-localsettings-incomplete": "Esamas failas <code>LocalSettings.php</code> yra nepilnas. Nenustatytas kintamasis $1.\nPrašome pakeisti failą <code>LocalSettings.php</code> tai, kad kintamasis būtų nustatytas ir spauskite „{{int:Config-continue}}“.",
        "config-localsettings-connection-error": "Buvo susidurta su klaida, kai jungtasi prie duomenų bazės naudojantis nustatymais iš <code>LocalSettings.php</code>. Prašome pataisyti šiuos nustatymus ir bandyti dar kartą.\n\n$1",
@@ -54,6 +54,7 @@
        "config-apc": "[http://www.php.net/apc APC] yra įdiegtas",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] yra įdiegtas",
        "config-diff3-bad": "GNU diff3 nerastas.",
+       "config-using-server": "Naudojant serverio pavadinimas „<nowiki>$1</nowiki>“.",
        "config-using-uri": "Naudojamas serverio URL „<nowiki>$1$2</nowiki>“.",
        "config-db-type": "Duomenų bazės tipas:",
        "config-db-host": "Duomenų bazės serveris:",
        "config-header-oracle": "Oracle nustatymai",
        "config-header-mssql": "„Microsoft“ SQL serverio nustatymai",
        "config-invalid-db-type": "Neteisingas duomenų bazės tipas",
+       "config-missing-db-name": "Privalote įvesti „{{int:config-db-name}}“ reikšmę.",
        "config-missing-db-host": "Privalote įvesti „{{int:config-db-host}}“ reikšmę.",
        "config-missing-db-server-oracle": "Privalote įvesti „{{int:config-db-host-oracle}}“ reikšmę.",
        "config-postgres-old": "PostgreSQL $1 ar vėlesnė yra reikalinga. Jūs turite $2.",
+       "config-sqlite-cant-create-db": "Nepavyko sukurti duomenų bazės failo <code>$1</code>.",
        "config-regenerate": "Pergeneruoti LocalSettings.php →",
        "config-db-web-account-same": "Naudoti tą pačią paskyrą kaip ir įdiegimui",
        "config-db-web-create": "Sukurti paskyrą, jeigu jos nėra",
index 30907e6..4f8f6b3 100644 (file)
@@ -72,10 +72,13 @@ class EmailNotification {
        protected $editor;
 
        /**
+        * @deprecated since 1.27 use WatchedItemStore::updateNotificationTimestamp directly
+        *
         * @param User $editor The editor that triggered the update.  Their notification
         *  timestamp will not be updated(they have already seen it)
         * @param LinkTarget $linkTarget The link target of the title to update timestamps for
         * @param string $timestamp Set the update timestamp to this value
+        *
         * @return int[] Array of user IDs
         */
        public static function updateWatchlistTimestamp(
@@ -83,47 +86,16 @@ class EmailNotification {
                LinkTarget $linkTarget,
                $timestamp
        ) {
-               global $wgEnotifWatchlist, $wgShowUpdatedMarker;
-
-               if ( !$wgEnotifWatchlist && !$wgShowUpdatedMarker ) {
+               // wfDeprecated( __METHOD__, '1.27' );
+               $config = RequestContext::getMain()->getConfig();
+               if ( !$config->get( 'EnotifWatchlist' ) && !$config->get( 'ShowUpdatedMarker' ) ) {
                        return [];
                }
-
-               $dbw = wfGetDB( DB_MASTER );
-               $res = $dbw->select( [ 'watchlist' ],
-                       [ 'wl_user' ],
-                       [
-                               'wl_user != ' . intval( $editor->getID() ),
-                               'wl_namespace' => $linkTarget->getNamespace(),
-                               'wl_title' => $linkTarget->getDBkey(),
-                               'wl_notificationtimestamp IS NULL',
-                       ], __METHOD__
+               return WatchedItemStore::getDefaultInstance()->updateNotificationTimestamp(
+                       $editor,
+                       $linkTarget,
+                       $timestamp
                );
-
-               $watchers = [];
-               foreach ( $res as $row ) {
-                       $watchers[] = intval( $row->wl_user );
-               }
-
-               if ( $watchers ) {
-                       // Update wl_notificationtimestamp for all watching users except the editor
-                       $fname = __METHOD__;
-                       $dbw->onTransactionIdle(
-                               function () use ( $dbw, $timestamp, $watchers, $linkTarget, $fname ) {
-                                       $dbw->update( 'watchlist',
-                                               [ /* SET */
-                                                       'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
-                                               ], [ /* WHERE */
-                                                       'wl_user' => $watchers,
-                                                       'wl_namespace' => $linkTarget->getNamespace(),
-                                                       'wl_title' => $linkTarget->getDBkey(),
-                                               ], $fname
-                                       );
-                               }
-                       );
-               }
-
-               return $watchers;
        }
 
        /**
@@ -149,7 +121,15 @@ class EmailNotification {
                }
 
                // update wl_notificationtimestamp for watchers
-               $watchers = self::updateWatchlistTimestamp( $editor, $title, $timestamp );
+               $config = RequestContext::getMain()->getConfig();
+               $watchers = [];
+               if ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) ) {
+                       $watchers = WatchedItemStore::getDefaultInstance()->updateNotificationTimestamp(
+                               $editor,
+                               $title,
+                               $timestamp
+                       );
+               }
 
                $sendEmail = true;
                // $watchers deals with $wgEnotifWatchlist.
index d65e8be..d7ba266 100644 (file)
@@ -4636,7 +4636,7 @@ class Parser {
                        $anchor = $safeHeadline;
                        $legacyAnchor = $legacyHeadline;
                        if ( isset( $refers[$arrayKey] ) ) {
-                               // @codingStandardsIgnoreStart 
+                               // @codingStandardsIgnoreStart
                                for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
                                // @codingStandardsIgnoreEnd
                                $anchor .= "_$i";
@@ -4645,7 +4645,7 @@ class Parser {
                                $refers[$arrayKey] = true;
                        }
                        if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
-                               // @codingStandardsIgnoreStart 
+                               // @codingStandardsIgnoreStart
                                for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
                                // @codingStandardsIgnoreEnd
                                $legacyAnchor .= "_$i";
@@ -4793,7 +4793,7 @@ class Parser {
                        $sections[0] = $sections[0] . $toc . "\n";
                }
 
-               $full .= join( '', $sections );
+               $full .= implode( '', $sections );
 
                if ( $this->mForceTocPosition ) {
                        return str_replace( '<!--MWTOC-->', $toc, $full );
index 598702d..5e8fb04 100644 (file)
@@ -160,7 +160,7 @@ class SearchMssql extends SearchDatabase {
                        }
                }
 
-               $searchon = $this->db->addQuotes( join( ',', $q ) );
+               $searchon = $this->db->addQuotes( implode( ',', $q ) );
                $field = $this->getIndexField( $fulltext );
                return "$field, $searchon";
        }
index 70c771d..bbdfdc3 100644 (file)
@@ -120,7 +120,7 @@ class BotPasswordSessionProvider extends ImmutableSessionProviderWithCookie {
                if ( $missingKeys ) {
                        $this->logger->info( 'Session "{session}": Missing metadata: {missing}', [
                                'session' => $info,
-                               'missing' => join( ', ', $missingKeys ),
+                               'missing' => implode( ', ', $missingKeys ),
                        ] );
                        return false;
                }
index 21db609..0fd8fa8 100644 (file)
@@ -352,7 +352,7 @@ final class Session implements \Countable, \Iterator, \ArrayAccess {
                        $new = true;
                }
                if ( is_array( $salt ) ) {
-                       $salt = join( '|', $salt );
+                       $salt = implode( '|', $salt );
                }
                return new Token( $secret, (string)$salt, $new );
        }
index 81f8243..efa3445 100644 (file)
@@ -287,7 +287,7 @@ final class SessionManager implements SessionManagerInterface {
                // Make sure there's exactly one
                if ( count( $infos ) > 1 ) {
                        throw new \UnexpectedValueException(
-                               'Multiple empty sessions tied for top priority: ' . join( ', ', $infos )
+                               'Multiple empty sessions tied for top priority: ' . implode( ', ', $infos )
                        );
                } elseif ( count( $infos ) < 1 ) {
                        throw new \UnexpectedValueException( 'No provider could provide an empty session!' );
@@ -537,7 +537,7 @@ final class SessionManager implements SessionManagerInterface {
                \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
 
                # Watch user's userpage and talk page
-               $user->addWatch( $user->getUserPage(), \WatchedItem::IGNORE_USER_RIGHTS );
+               $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
 
                return true;
        }
@@ -677,7 +677,7 @@ final class SessionManager implements SessionManagerInterface {
 
                if ( count( $retInfos ) > 1 ) {
                        $ex = new \OverflowException(
-                               'Multiple sessions for this request tied for top priority: ' . join( ', ', $retInfos )
+                               'Multiple sessions for this request tied for top priority: ' . implode( ', ', $retInfos )
                        );
                        $ex->sessionInfos = $retInfos;
                        throw $ex;
index d6e0377..92311b5 100644 (file)
@@ -248,6 +248,30 @@ class SkinTemplate extends Skin {
 
        }
 
+       /**
+        * Wrap the body text with language information and identifiable element
+        *
+        * @param Title $title
+        * @return string html
+        */
+       protected function wrapHTML( $title, $html ) {
+               # An ID that includes the actual body text; without categories, contentSub, ...
+               $realBodyAttribs = [ 'id' => 'mw-content-text' ];
+
+               # Add a mw-content-ltr/rtl class to be able to style based on text direction
+               # when the content is different from the UI language, i.e.:
+               # not for special pages or file pages AND only when viewing
+               if ( !in_array( $title->getNamespace(), [ NS_SPECIAL, NS_FILE ] ) &&
+                       Action::getActionName( $this ) === 'view' ) {
+                       $pageLang = $title->getPageViewLanguage();
+                       $realBodyAttribs['lang'] = $pageLang->getHtmlCode();
+                       $realBodyAttribs['dir'] = $pageLang->getDir();
+                       $realBodyAttribs['class'] = 'mw-content-' . $pageLang->getDir();
+               }
+
+               return Html::rawElement( 'div', $realBodyAttribs, $html );
+       }
+
        /**
         * initialize various variables and generate the template
         *
@@ -424,22 +448,8 @@ class SkinTemplate extends Skin {
                $tpl->set( 'sitenotice', $this->getSiteNotice() );
                $tpl->set( 'bottomscripts', $this->bottomScripts() );
                $tpl->set( 'printfooter', $this->printSource() );
-
-               # An ID that includes the actual body text; without categories, contentSub, ...
-               $realBodyAttribs = [ 'id' => 'mw-content-text' ];
-
-               # Add a mw-content-ltr/rtl class to be able to style based on text direction
-               # when the content is different from the UI language, i.e.:
-               # not for special pages or file pages AND only when viewing
-               if ( !in_array( $title->getNamespace(), [ NS_SPECIAL, NS_FILE ] ) &&
-                       Action::getActionName( $this ) === 'view' ) {
-                       $pageLang = $title->getPageViewLanguage();
-                       $realBodyAttribs['lang'] = $pageLang->getHtmlCode();
-                       $realBodyAttribs['dir'] = $pageLang->getDir();
-                       $realBodyAttribs['class'] = 'mw-content-' . $pageLang->getDir();
-               }
-
-               $out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
+               // Wrap the bodyText with #mw-content-text element
+               $out->mBodytext = $this->wrapHTML( $title, $out->mBodytext );
                $tpl->setRef( 'bodytext', $out->mBodytext );
 
                $language_urls = $this->getLanguages();
index 9930655..625e4aa 100644 (file)
@@ -794,7 +794,7 @@ class SpecialBlock extends FormSpecialPage {
                        WatchAction::doWatch(
                                Title::makeTitle( NS_USER, $target ),
                                $performer,
-                               WatchedItem::IGNORE_USER_RIGHTS
+                               User::IGNORE_USER_RIGHTS
                        );
                }
 
index 6b61ef9..8d45468 100644 (file)
@@ -714,7 +714,7 @@ class LoginForm extends SpecialPage {
                DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
 
                // Watch user's userpage and talk page
-               $u->addWatch( $u->getUserPage(), WatchedItem::IGNORE_USER_RIGHTS );
+               $u->addWatch( $u->getUserPage(), User::IGNORE_USER_RIGHTS );
 
                return Status::newGood( $u );
        }
index c1e538a..a874038 100644 (file)
@@ -716,7 +716,7 @@ abstract class UploadBase {
                                WatchAction::doWatch(
                                        $this->getLocalFile()->getTitle(),
                                        $user,
-                                       WatchedItem::IGNORE_USER_RIGHTS
+                                       User::IGNORE_USER_RIGHTS
                                );
                        }
                        Hooks::run( 'UploadComplete', [ &$this ] );
index 7bc410d..68a169a 100644 (file)
@@ -62,17 +62,22 @@ class User implements IDBAccessObject {
         */
        const VERSION = 10;
 
-       /**
-        * Maximum items in $mWatchedItems
-        */
-       const MAX_WATCHED_ITEMS_CACHE = 100;
-
        /**
         * Exclude user options that are set to their default value.
         * @since 1.25
         */
        const GETOPTIONS_EXCLUDE_DEFAULTS = 1;
 
+       /**
+        * @since 1.27
+        */
+       const CHECK_USER_RIGHTS = true;
+
+       /**
+        * @since 1.27
+        */
+       const IGNORE_USER_RIGHTS = false;
+
        /**
         * Array of Strings List of member variables which are saved to the
         * shared cache (memcached). Any operation which changes the
@@ -291,9 +296,6 @@ class User implements IDBAccessObject {
        /** @var Block */
        private $mBlockedFromCreateAccount = false;
 
-       /** @var array */
-       private $mWatchedItems = [];
-
        /** @var integer User::READ_* constant bitfield used to load data */
        protected $queryFlagsUsed = self::READ_NORMAL;
 
@@ -3445,51 +3447,36 @@ class User implements IDBAccessObject {
                }
        }
 
-       /**
-        * Get a WatchedItem for this user and $title.
-        *
-        * @since 1.22 $checkRights parameter added
-        * @param Title $title
-        * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
-        *     Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
-        * @return WatchedItem
-        */
-       public function getWatchedItem( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
-               $key = $checkRights . ':' . $title->getNamespace() . ':' . $title->getDBkey();
-
-               if ( isset( $this->mWatchedItems[$key] ) ) {
-                       return $this->mWatchedItems[$key];
-               }
-
-               if ( count( $this->mWatchedItems ) >= self::MAX_WATCHED_ITEMS_CACHE ) {
-                       $this->mWatchedItems = [];
-               }
-
-               $this->mWatchedItems[$key] = WatchedItem::fromUserTitle( $this, $title, $checkRights );
-               return $this->mWatchedItems[$key];
-       }
-
        /**
         * Check the watched status of an article.
         * @since 1.22 $checkRights parameter added
         * @param Title $title Title of the article to look at
-        * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
-        *     Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
+        * @param bool $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+        *     Pass User::CHECK_USER_RIGHTS or User::IGNORE_USER_RIGHTS.
         * @return bool
         */
-       public function isWatched( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
-               return $this->getWatchedItem( $title, $checkRights )->isWatched();
+       public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
+               if ( $title->isWatchable() && ( !$checkRights || $this->isAllowed( 'viewmywatchlist' ) ) ) {
+                       return WatchedItemStore::getDefaultInstance()->isWatched( $this, $title );
+               }
+               return false;
        }
 
        /**
         * Watch an article.
         * @since 1.22 $checkRights parameter added
         * @param Title $title Title of the article to look at
-        * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
-        *     Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
-        */
-       public function addWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
-               $this->getWatchedItem( $title, $checkRights )->addWatch();
+        * @param bool $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+        *     Pass User::CHECK_USER_RIGHTS or User::IGNORE_USER_RIGHTS.
+        */
+       public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
+               if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
+                       WatchedItemStore::getDefaultInstance()->addWatchBatch( [
+                               [ $this, $title->getSubjectPage() ],
+                               [ $this, $title->getTalkPage() ],
+                       ]
+                       );
+               }
                $this->invalidateCache();
        }
 
@@ -3497,11 +3484,14 @@ class User implements IDBAccessObject {
         * Stop watching an article.
         * @since 1.22 $checkRights parameter added
         * @param Title $title Title of the article to look at
-        * @param int $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
-        *     Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
+        * @param bool $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
+        *     Pass User::CHECK_USER_RIGHTS or User::IGNORE_USER_RIGHTS.
         */
-       public function removeWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
-               $this->getWatchedItem( $title, $checkRights )->removeWatch();
+       public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
+               if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
+                       WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getSubjectPage() );
+                       WatchedItemStore::getDefaultInstance()->removeWatch( $this, $title->getTalkPage() );
+               }
                $this->invalidateCache();
        }
 
@@ -3569,9 +3559,8 @@ class User implements IDBAccessObject {
                        $force = 'force';
                }
 
-               $this->getWatchedItem( $title )->resetNotificationTimestamp(
-                       $force, $oldid
-               );
+               WatchedItemStore::getDefaultInstance()
+                       ->resetNotificationTimestamp( $this, $title, $force, $oldid );
        }
 
        /**
index 521e345..617e8f5 100644 (file)
@@ -69,13 +69,13 @@ class MWRestrictions {
                $invalidKeys = array_diff( $keys, $validKeys );
                if ( $invalidKeys ) {
                        throw new InvalidArgumentException(
-                               'Array contains invalid keys: ' . join( ', ', $invalidKeys )
+                               'Array contains invalid keys: ' . implode( ', ', $invalidKeys )
                        );
                }
                $missingKeys = array_diff( $neededKeys, $keys );
                if ( $missingKeys ) {
                        throw new InvalidArgumentException(
-                               'Array is missing required keys: ' . join( ', ', $missingKeys )
+                               'Array is missing required keys: ' . implode( ', ', $missingKeys )
                        );
                }
 
index b36f080..89625c0 100644 (file)
@@ -52,11 +52,11 @@ class LanguageCu extends Language {
                if ( !preg_match( "/[a-zA-Z_]/us", $word ) ) {
                        switch ( $case ) {
                                case 'genitive': # родительный падеж
-                                       if ( ( join( '', array_slice( $ar[0], -4 ) ) == 'вики' )
-                                               || ( join( '', array_slice( $ar[0], -4 ) ) == 'Вики' )
+                                       if ( ( implode( '', array_slice( $ar[0], -4 ) ) == 'вики' )
+                                               || ( implode( '', array_slice( $ar[0], -4 ) ) == 'Вики' )
                                        ) {
-                                       } elseif ( join( '', array_slice( $ar[0], -2 ) ) == 'ї' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -2 ) ) . 'їѩ';
+                                       } elseif ( implode( '', array_slice( $ar[0], -2 ) ) == 'ї' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -2 ) ) . 'їѩ';
                                        }
                                        break;
                                case 'accusative': # винительный падеж
index f6d5270..05b0ebe 100644 (file)
@@ -52,12 +52,12 @@ class LanguageHy extends Language {
                if ( !preg_match( "/[a-zA-Z_]/us", $word ) ) {
                        switch ( $case ) {
                                case 'genitive': # սեռական հոլով
-                                       if ( join( '', array_slice( $ar[0], -1 ) ) == 'ա' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -1 ) ) . 'այի';
-                                       } elseif ( join( '', array_slice( $ar[0], -1 ) ) == 'ո' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -1 ) ) . 'ոյի';
-                                       } elseif ( join( '', array_slice( $ar[0], -4 ) ) == 'գիրք' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -4 ) ) . 'գրքի';
+                                       if ( implode( '', array_slice( $ar[0], -1 ) ) == 'ա' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -1 ) ) . 'այի';
+                                       } elseif ( implode( '', array_slice( $ar[0], -1 ) ) == 'ո' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -1 ) ) . 'ոյի';
+                                       } elseif ( implode( '', array_slice( $ar[0], -4 ) ) == 'գիրք' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -4 ) ) . 'գրքի';
                                        } else {
                                                $word .= 'ի';
                                        }
index 6cc23e3..72bde40 100644 (file)
@@ -51,19 +51,19 @@ class LanguageUk extends Language {
                if ( !preg_match( "/[a-zA-Z_]/us", $word ) ) {
                        switch ( $case ) {
                                case 'genitive': # родовий відмінок
-                                       if ( join( '', array_slice( $ar[0], -2 ) ) === 'ія' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -2 ) ) . 'ії';
-                                       } elseif ( join( '', array_slice( $ar[0], -2 ) ) === 'ти' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -2 ) ) . 'т';
-                                       } elseif ( join( '', array_slice( $ar[0], -2 ) ) === 'ди' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -2 ) ) . 'дів';
-                                       } elseif ( join( '', array_slice( $ar[0], -3 ) ) === 'ник' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -3 ) ) . 'ника';
+                                       if ( implode( '', array_slice( $ar[0], -2 ) ) === 'ія' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -2 ) ) . 'ії';
+                                       } elseif ( implode( '', array_slice( $ar[0], -2 ) ) === 'ти' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -2 ) ) . 'т';
+                                       } elseif ( implode( '', array_slice( $ar[0], -2 ) ) === 'ди' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -2 ) ) . 'дів';
+                                       } elseif ( implode( '', array_slice( $ar[0], -3 ) ) === 'ник' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -3 ) ) . 'ника';
                                        }
                                        break;
                                case 'accusative': # знахідний відмінок
-                                       if ( join( '', array_slice( $ar[0], -2 ) ) === 'ія' ) {
-                                               $word = join( '', array_slice( $ar[0], 0, -2 ) ) . 'ію';
+                                       if ( implode( '', array_slice( $ar[0], -2 ) ) === 'ія' ) {
+                                               $word = implode( '', array_slice( $ar[0], 0, -2 ) ) . 'ію';
                                        }
                                        break;
                        }
index 5131276..a1167f2 100644 (file)
@@ -84,7 +84,7 @@
        "september": "سبتامبر",
        "october": "كتوبر",
        "november": "نوفامبر",
-       "december": "ديسامبر",
+       "december": "ديسمبر",
        "january-gen": "جانفي",
        "february-gen": "فيفري",
        "march-gen": "مارس",
        "unprotectthispage": "بدّل الحضية تاع هاد الباجة",
        "newpage": "باجه جديده",
        "talkpage": "قرعَج على هاد الباجة",
-       "talkpagelinktext": "مهادرة",
+       "talkpagelinktext": "تناقش",
        "specialpage": "باجة خوصوصيّة",
        "personaltools": "دوزان شخصيه",
        "articlepage": "شوف الباجة تاع المحتاوا",
        "pool-errorunknown": "خلطة ماشي معروفة",
        "pool-servererror": "السربيس تاع العدّان راه حابس ( $1 ).",
        "poolcounter-usage-error": "غلطة تاع ستُعمال: $1",
-       "aboutsite": "على{{SITENAME}}",
+       "aboutsite": "على {{SITENAME}}",
        "aboutpage": "Project:على",
        "copyright": "المحتاوا راه تحت النسخة $1 تاع الليسانس، غير يلا كان مكتوب حاجاخرة.",
        "copyrightpage": "{{ns:project}}:حقوق النسخ",
        "currentevents": "الختيّارات",
        "currentevents-url": "Project:خبورات",
        "disclaimers": "تنبيهات",
-       "disclaimerpage": "Project:التحذيرات العامه",
+       "disclaimerpage": "Project:التنبيهات العامة",
        "edithelp": "معونة",
        "helppage-top-gethelp": "معاونة",
        "mainpage": "الپاجة اللولانيّة",
        "policy-url": "Project:المقاون",
        "portal": "بورطاي المجتمع",
        "portal-url": "Project:بورطاي المجتمع",
-       "privacy": "اÙ\84سÙ\8aاسة ØªØ§Ø¹ Ø§Ù\84خصÙ\88صÙ\8aات (اÙ\84دÙ\8aÙ\86 Ø§Ù\84ضÙ\8aÙ\91Ù\82)",
-       "privacypage": "Project:خصÙ\88صÙ\8aÙ\87",
+       "privacy": "اÙ\84سÙ\8aاسة ØªØ§Ø¹ Ø§Ù\84خصÙ\88صÙ\8aØ©",
+       "privacypage": "Project:اÙ\84سÙ\8aاسة ØªØ§Ø¹ Ø§Ù\84خصÙ\88صÙ\8aÙ\91Ø©",
        "badaccess": "مشكل فل مسموحات",
        "badaccess-group0": "ماشي مقبول ليك تدير الشي الّي راك تسيّي تديرهُ.",
        "badaccess-groups": "الفعلة الّي راك سيّيت تديرها مسموحة برك لل مستعملّين {{PLURAL:$2||الّي هوما منل جماعة|الّي هوما من وحدة من هاد الجمايع}}: $1.",
        "viewsourceold": "شوف الاصل",
        "editlink": "تبديل",
        "viewsourcelink": "شوف العين",
-       "editsectionhint": "Ø¥Ù\8aدÙ\8aتÙ\8a Ø§Ù\84صÙ\86Ù\81:$1",
+       "editsectionhint": "بدÙ\91Ù\84 Ù\81 Ø§Ù\84سÙ\83سÙ\8aÙ\88Ù\86$1",
        "toc": "محتويات",
        "showtoc": "ورّي",
        "hidetoc": "خبّي",
        "site-atom-feed": "$1 تيار آتوم",
        "page-rss-feed": "تلقيمة RSS تاع \"$1\"",
        "page-atom-feed": "$1 تيار آتوم",
-       "red-link-title": "$1 (اÙ\84باجة Ù\85ا Ù\83اÙ\8aÙ\86Ø´)",
+       "red-link-title": "$1 (اÙ\84باجة Ù\85ا Ù\83اÙ\8aÙ\86Ø© Ø´Ù\8a)",
        "sort-descending": "رتّب بل نازولي",
        "sort-ascending": "رتّب بل طالوعي",
        "nstab-main": "الباجة",
        "myprivateinfoprotected": "ما عندكش السراح باش تبدّل المعلومات الشخصيّة ديالك.",
        "mypreferencesprotected": "ما عندكش السراح باش تبدّل الإختيّارات ديالك.",
        "ns-specialprotected": "الپاجات الخصيصة ما تنجمش تتبدّل.",
-       "titleprotected": "هاد العلوان راه محضي من` التبدال من عند [[User:$1|$1]].\nالسبّة الّي مدّها هيّا <em>$2</em>.",
+       "titleprotected": "[[User:$1|$1]] {{GENDER:$1|حمى|حمات}} هاذ العنوان م الخلقان.\nالسبّة تاعو هيّ <em>$2</em>.",
        "filereadonlyerror": "ماشي ممكن تبدال الفيشي \"$1\" خاطرش الزمّام \"$2\" راه مشغّلة فيه الخاصيّة \"اقرا برك\".\n\nالموسيّر الّي غلقهُ راه ماد التفسير هادا: \"$3\".",
        "invalidtitle-knownnamespace": "علوان ماشي مقبول ب وسَع تاع تسميّة \"$2\" معا الكتيبة \"$3\".",
        "invalidtitle-unknownnamespace": "علوان ماشي مقبول ب نمرو ماشي معروف تاع الوسَع تاع تسميّة \"$1\" معا الكتيبة \"$2\".",
        "minoreditletter": "ط",
        "newpageletter": "ج‌",
        "boteditletter": "ب",
-       "rc-change-size-new": "$1 {{PLURAL:$1|octet|octets}} مورا التبدال",
+       "rc-change-size-new": "$1 {{PLURAL:$1|اوكتاتة|اوكتاتات}} بعد التبدال",
        "rc-enhanced-expand": "شوف التفاصيل",
        "rc-enhanced-hide": "خبي التفاصيل",
        "recentchangeslinked": "تبديلات مربوطه",
index 53259dc..356080e 100644 (file)
        "grant-protect": "Абарона і зьняцьце абароны старонак",
        "grant-rollback": "Адкат зьменаў старонак",
        "grant-sendemail": "Адпраўка лістоў электроннай пошты іншым удзельнікам",
+       "grant-uploadeditmovefile": "Загрузка, замена і перайменаваньне файлаў",
        "newuserlogpage": "Журнал стварэньня рахункаў",
        "newuserlogpagetext": "Гэта журнал стварэньня рахункаў удзельнікаў і ўдзельніц.",
        "rightslog": "Журнал правоў удзельнікаў",
index 7188b0c..c4b6b67 100644 (file)
        "recentchanges-label-plusminus": "تاکدیمئ حجم بئ ای اندازگ بایٹ ئا تغیر کورته",
        "recentchanges-legend-heading": "<strong>اختصارئان:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (همیرنگ بی [[Special:NewPages|نوکین تاکدیمانئ لڑ لیست]] ئا سیل بکنیت)",
-       "rcnotefrom": "بئ جهلگا تغیرات شه <strong>$3, $4</strong> (تا <strong>$1</strong> {{PLURAL:$5|نشان داته بوته|نشان داته انت}}).",
+       "rcnotefrom": "بِه جهلگا تغیرات شه <strong>$3, $4</strong> (تا <strong>$1</strong> {{PLURAL:$5|نشان داته بوته|نشان داته انت}}).",
        "rclistfrom": "نوکین تغیراتانئ نشان داتین شرو شه $3 $2",
        "rcshowhideminor": "$1 ئی کمین ایڈیٹان",
        "rcshowhideminor-show": "نشان داتین",
index 7824732..8185527 100644 (file)
        "grant-editmyoptions": "আপনার ব্যবহারকারী পছন্দসমূহ সম্পাদনা করুন",
        "grant-editmywatchlist": "আপনার নজরতালিকা সম্পাদনা করুন",
        "grant-editprotected": "সংরক্ষিত পাতা সম্পাদনা করুন",
+       "grant-sendemail": "অন্য ব্যবহারকারীকে ইমেইল পাঠান",
        "grant-uploadfile": "নতুন ফাইল আপলোড করুন",
        "grant-basic": "মৌলিক অধিকার",
        "grant-viewdeleted": "অপসারিত ফাইল ও পাতাগুলি দেখুন",
index a3be6ef..cdb14db 100644 (file)
        "userrights": "Benutzerrechte verwalten",
        "userrights-lookup-user": "Gruppenzugehörigkeit verwalten",
        "userrights-user-editname": "Benutzername:",
-       "editusergroup": "{{GENDER:$1|Benutzerrechte}} bearbeiten",
+       "editusergroup": "{{GENDER:$1|Benutzergruppen}} verwalten",
        "editinguser": "Ändere Benutzerrechte {{GENDER:$1|des Benutzers|der Benutzerin}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Benutzer-Gruppenzugehörigkeit bearbeiten",
        "saveusergroups": "{{GENDER:$1|Gruppenzugehörigkeit}} ändern",
        "right-editprotected": "Seiten bearbeiten, die als „{{int:protect-level-sysop}}“ geschützt sind",
        "right-editsemiprotected": "Seiten bearbeiten, die als „{{int:protect-level-autoconfirmed}}“ geschützt sind",
        "right-editcontentmodel": "Das Inhaltsmodell einer Seite bearbeiten",
-       "right-editinterface": "Benutzeroberfläche bearbeiten",
+       "right-editinterface": "Systemnachrichten und Benutzeroberflächen bearbeiten",
        "right-editusercssjs": "Fremde CSS- und JavaScript-Dateien bearbeiten",
        "right-editusercss": "Fremde CSS-Dateien bearbeiten",
        "right-edituserjs": "Fremde JavaScript-Dateien bearbeiten",
        "right-patrolmarks": "Kontrollmarkierungen in den letzten Änderungen sehen",
        "right-unwatchedpages": "Liste der unbeobachteten Seiten ansehen",
        "right-mergehistory": "Versionsgeschichten von Seiten vereinen",
-       "right-userrights": "Benutzerrechte bearbeiten",
-       "right-userrights-interwiki": "Benutzerrechte in anderen Wikis bearbeiten",
+       "right-userrights": "Benutzerrechte verwalten",
+       "right-userrights-interwiki": "Benutzerrechte in anderen Wikis verwalten",
        "right-siteadmin": "Datenbank sperren und entsperren",
        "right-override-export-depth": "Exportiere Seiten einschließlich verlinkter Seiten bis zu einer Tiefe von 5",
        "right-sendemail": "E-Mails an andere Benutzer senden",
        "action-autopatrol": "eigene Bearbeitungen als kontrolliert zu markieren",
        "action-unwatchedpages": "die Liste der unbeobachteten Seiten einzusehen",
        "action-mergehistory": "die Versionsgeschichten von Seiten zu vereinen",
-       "action-userrights": "Benutzerrechte zu ändern",
+       "action-userrights": "Benutzerrechte zu verwalten",
        "action-userrights-interwiki": "die Rechte von Benutzern in anderen Wikis zu ändern",
        "action-siteadmin": "die Datenbank zu sperren oder freizugeben",
        "action-sendemail": "E-Mails zu senden",
        "changecontentmodel-nodirectediting": "Das Inhaltsmodell „$1“ unterstützt keine direkten Bearbeitungen",
        "log-name-contentmodel": "Inhaltsmodell-Änderungs-Logbuch",
        "log-description-contentmodel": "Ereignisse bezüglich den Inhaltsmodellen einer Seite",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|erstellte}} die Seite $3 mit einem Nicht-Standard-Inhaltsmodell „$5“",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|änderte}} das Inhaltsmodell der Seite $3 von „$4“ nach „$5“",
        "logentry-contentmodel-change-revertlink": "zurücksetzen",
        "logentry-contentmodel-change-revert": "zurücksetzen",
index df8a1c3..13fe7ce 100644 (file)
        "right-importupload": "Εισαγωγή σελίδων με ανέβασμα αρχείου",
        "right-patrol": "Σήμανση επεξεργασιών άλλων χρηστών ως ελεγμένες",
        "right-autopatrol": "Αυτόματη σημείωση των επεξεργασιών τους ως ελεγμένες",
-       "right-patrolmarks": "Î\94είÏ\84ε Ï\84ιÏ\82 Ï\83ημειÏ\8eÏ\83ειÏ\82 'Ï\85Ï\80Ï\8c Ï\80αÏ\81ακολοÏ\8dθηÏ\83η' Ï\84Ï\89ν Ï\80Ï\81οÏ\83Ï\86άτων αλλαγών",
+       "right-patrolmarks": "ΠÏ\81οβολή Ï\83ημάνÏ\83εÏ\89ν ÎµÎ»Î­Î³Ï\87οÏ\85 Ï\80Ï\81Ï\8cÏ\83Ï\86ατων αλλαγών",
        "right-unwatchedpages": "Δείτε μια λίστα σελίδων που δεν παρακολουθούνται",
        "right-mergehistory": "Συγχώνευση του ιστορικού των σελίδων",
        "right-userrights": "Τροποποίηση όλων των δικαιωμάτων χρηστών",
        "pageinfo-category-pages": "Αριθμός σελίδων",
        "pageinfo-category-subcats": "Αριθμός υποκατηγοριών",
        "pageinfo-category-files": "Αριθμός αρχείων",
-       "markaspatrolleddiff": "Î\9dα Ï\83ημειÏ\89θεί 'Ï\85Ï\80Ï\8c Ï\80αÏ\81ακολοÏ\8dθηÏ\83η'",
+       "markaspatrolleddiff": "ΣήμανÏ\83η Ï\89Ï\82 ÎµÎ»ÎµÎ³Î¼Î­Î½Î¿",
        "markaspatrolledtext": "Σήμανση αυτής της σελίδας ως ελεγμένης",
        "markaspatrolledtext-file": "Επισημάνετε αυτή τη έκδοση του αρχείου ως ελεγμένη",
-       "markedaspatrolled": "ΣημειÏ\89μένο Ï\89Ï\82 'Ï\85Ï\80Ï\8c Ï\80αÏ\81ακολοÏ\8dθηÏ\83η'",
+       "markedaspatrolled": "ΣημειÏ\8eθηκε Ï\89Ï\82 ÎµÎ»ÎµÎ³Î¼Î­Î½Î¿",
        "markedaspatrolledtext": "Η επιλεγμένη αναθεώρηση της [[:$1]] έχει σημειωθεί ως ελεγμένη.",
        "rcpatroldisabled": "Η λειτουργία 'Παρακολούθηση Πρόσφατων Αλλαγών' έχει απενεργοποιηθεί.",
        "rcpatroldisabledtext": "Η λειτουργία 'Παρακολούθηση Πρόσφατων Αλλαγών' είναι αυτή τη στιγμή απενεργοποιημένη.",
index 115b66f..fea9fbd 100644 (file)
        "nocookiesfornew": "The user account was not created, as we could not confirm its source.\nEnsure you have cookies enabled, reload this page and try again.",
        "nocookiesforlogin": "{{int:nocookieslogin}}",
        "noname": "You have not specified a valid username.",
-       "loginsuccesstitle": "Login successful",
+       "loginsuccesstitle": "Logged in",
        "loginsuccess": "<strong>You are now logged in to {{SITENAME}} as \"$1\".</strong>",
        "nosuchuser": "There is no user by the name \"$1\".\nUsernames are case sensitive.\nCheck your spelling, or [[Special:UserLogin/signup|create a new account]].",
        "nosuchusershort": "There is no user by the name \"$1\".\nCheck your spelling.",
        "createaccount-title": "Account creation for {{SITENAME}}",
        "createaccount-text": "Someone created an account for your email address on {{SITENAME}} ($4) named \"$2\", with password \"$3\".\nYou should log in and change your password now.\n\nYou may ignore this message, if this account was created in error.",
        "login-throttled": "You have made too many recent login attempts.\nPlease wait $1 before trying again.",
-       "login-abort-generic": "Your login was unsuccessful - Aborted",
+       "login-abort-generic": "Your login failed - Aborted",
        "login-migrated-generic": "Your account has been migrated, and your username no longer exist on this wiki.",
        "loginlanguagelabel": "Language: $1",
        "loginlanguagelinks": "* {{#language:de}}|de\n* {{#language:en}}|en\n* {{#language:eo}}|eo\n* {{#language:fr}}|fr\n* {{#language:es}}|es\n* {{#language:it}}|it\n* {{#language:nl}}|nl",
        "newpassword": "New password:",
        "retypenew": "Retype new password:",
        "resetpass_submit": "Set password and log in",
-       "changepassword-success": "Your password has been changed successfully!",
+       "changepassword-success": "Your password has been changed!",
        "changepassword-throttled": "You have made too many recent login attempts.\nPlease wait $1 before trying again.",
        "botpasswords": "Bot passwords",
        "botpasswords-summary": "<em>Bot passwords</em> allow access to a user account via the API without using the account's main login credentials. The user rights available when logged in with a bot password may be restricted.\n\nIf you don't know why you might want to do this, you should probably not do it. No one should ever ask you to generate one of these and give it to them.",
        "botpasswords-insert-failed": "Failed to add bot name \"$1\". Was it already added?",
        "botpasswords-update-failed": "Failed to update bot name \"$1\". Was it deleted?",
        "botpasswords-created-title": "Bot password created",
-       "botpasswords-created-body": "The bot password \"$1\" was created successfully.",
+       "botpasswords-created-body": "The bot password \"$1\" was created.",
        "botpasswords-updated-title": "Bot password updated",
-       "botpasswords-updated-body": "The bot password \"$1\" was updated successfully.",
+       "botpasswords-updated-body": "The bot password \"$1\" was updated.",
        "botpasswords-deleted-title": "Bot password deleted",
        "botpasswords-deleted-body": "The bot password \"$1\" was deleted.",
        "botpasswords-newpassword": "The new password to log in with <strong>$1</strong> is <strong>$2</strong>. <em>Please record this for future reference.</em>",
        "resetpass-no-info": "You must be logged in to access this page directly.",
        "resetpass-submit-loggedin": "Change password",
        "resetpass-submit-cancel": "Cancel",
-       "resetpass-wrong-oldpass": "Invalid temporary or current password.\nYou may have already successfully changed your password or requested a new temporary password.",
+       "resetpass-wrong-oldpass": "Invalid temporary or current password.\nYou may have already changed your password or requested a new temporary password.",
        "resetpass-recycled": "Please reset your password to something other than your current password.",
        "resetpass-temp-emailed": "You logged in with a temporary emailed code.\nTo finish logging in, you must set a new password here:",
        "resetpass-temp-password": "Temporary password:",
        "revdelete-unsuppress": "Remove restrictions on restored revisions",
        "revdelete-log": "Reason:",
        "revdelete-submit": "Apply to selected {{PLURAL:$1|revision|revisions}}",
-       "revdelete-success": "Revision visibility successfully updated.",
+       "revdelete-success": "Revision visibility updated.",
        "revdelete-failure": "Revision visibility could not be updated:\n$1",
-       "logdelete-success": "Log visibility successfully set.",
+       "logdelete-success": "Log visibility set.",
        "logdelete-failure": "Log visibility could not be set:\n$1",
        "revdel-restore": "change visibility",
        "pagehist": "Page history",
        "userrights-unchangeable-col": "Groups you cannot change",
        "userrights-irreversible-marker": "$1*",
        "userrights-conflict": "Conflict of user rights changes! Please review and confirm your changes.",
-       "userrights-removed-self": "You successfully removed your own rights. As such, you are no longer able to access this page.",
+       "userrights-removed-self": "You removed your own rights. As such, you are no longer able to access this page.",
        "group": "Group:",
        "group-user": "Users",
        "group-autoconfirmed": "Autoconfirmed users",
        "uploadstash-summary": "This page provides access to files that are uploaded or in the process of uploading, but are not yet published to the wiki. These files are not visible to anyone but the user who uploaded them.",
        "uploadstash-clear": "Clear stashed files",
        "uploadstash-nofiles": "You have no stashed files.",
-       "uploadstash-badtoken": "Performing of that action was unsuccessful, perhaps because your editing credentials expired. Try again.",
-       "uploadstash-errclear": "Clearing the files was unsuccessful.",
+       "uploadstash-badtoken": "Performing that action failed. Perhaps because your editing credentials expired. Please try again.",
+       "uploadstash-errclear": "Clearing the files failed.",
        "uploadstash-refresh": "Refresh the list of files",
        "invalid-chunk-offset": "Invalid chunk offset",
        "img-auth-accessdenied": "Access denied",
        "tags-delete-not-allowed": "Tags defined by an extension cannot be deleted unless the extension specifically allows it.",
        "tags-delete-not-found": "The tag \"$1\" does not exist.",
        "tags-delete-too-many-uses": "The tag \"$1\" is applied to more than $2 {{PLURAL:$2|revision|revisions}}, which means it cannot be deleted.",
-       "tags-delete-warnings-after-delete": "The tag \"$1\" was deleted successfully, but the following {{PLURAL:$2|warning was|warnings were}} encountered:",
+       "tags-delete-warnings-after-delete": "The tag \"$1\" was deleted, but the following {{PLURAL:$2|warning was|warnings were}} encountered:",
        "tags-activate-title": "Activate tag",
        "tags-activate-question": "You are about to activate the tag \"$1\".",
        "tags-activate-reason": "Reason:",
        "tags-edit-reason": "Reason:",
        "tags-edit-revision-submit": "Apply changes to {{PLURAL:$1|this revision|$1 revisions}}",
        "tags-edit-logentry-submit": "Apply changes to {{PLURAL:$1|this log entry|$1 log entries}}",
-       "tags-edit-success": "The changes were successfully applied.",
+       "tags-edit-success": "The changes were applied.",
        "tags-edit-failure": "The changes could not be applied:\n$1",
        "tags-edit-nooldid-title": "Invalid target revision",
        "tags-edit-nooldid-text": "You have either not specified any target revision on which to perform this function, or the specified revision does not exist.",
index 79e1cfd..21ecee3 100644 (file)
        "changecontentmodel-nodirectediting": "El modelo de contenido $1 no admite la edición directa",
        "log-name-contentmodel": "Registro de cambios del modelo de contenido",
        "log-description-contentmodel": "Eventos relacionados con los modelos de contenido de una página",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|creó}} la página $3 usando un modelo de contenido no predeterminado \"$5\"",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|cambió}} el modelo de contenido de la página $3 de \"$4\" a \"$5\"",
        "logentry-contentmodel-change-revertlink": "revertir",
        "logentry-contentmodel-change-revert": "revertir",
index a6a5652..7b007e8 100644 (file)
        "changecontentmodel-nodirectediting": "نمونه محتوی $1 امکان ویرایش مستقیم را پشتیبانی نمی‌کند",
        "log-name-contentmodel": "سیاهه تغییر نمونه محتوی",
        "log-description-contentmodel": "رویدادهای مرتبط با نمونه محتوی‌های یک صفحه",
+       "logentry-contentmodel-new": "صفحهٔ $3 با استفاده از مدل‌های محتوایی غیر پیش‌فرض «$5» توسط $1 {{GENDER:$2|ساخته شد}}",
        "logentry-contentmodel-change": "نمونه محتوای صفحهٔ $3 از \"$4\" به \"$5\" توسط $1 {{GENDER:$2|تغییر داده شد}}",
        "logentry-contentmodel-change-revertlink": "واگردانی",
        "logentry-contentmodel-change-revert": "واگردانی",
index 2cac81f..805ed0e 100644 (file)
@@ -18,6 +18,7 @@
        "tog-hideminor": "Krógva minni broytingar í seinastu broytingum",
        "tog-hidepatrolled": "Krógva eftirkannaðar rættingar í seinastu broytingum",
        "tog-newpageshidepatrolled": "Goym eftirkannaðar síður frá listanum yvir nýggjar síður",
+       "tog-hidecategorization": "Fjal bólking av síðum",
        "tog-extendwatchlist": "Víðka eftirlitslistan fyri at vísa allar broytingar, ikki bara tær seinastu",
        "tog-usenewrc": "Bólka broytingar eftir síðu í seinastu broytingum og eftirlitslita",
        "tog-numberheadings": "Sjálvvirkandi talmerking av yvirskriftum",
@@ -46,6 +47,7 @@
        "tog-watchlisthideliu": "Goym broytingar sum eru gjørdar av brúkarum, sum eru loggaðir á, frá hyggjaralistanum",
        "tog-watchlisthideanons": "Krógva broytingar sum eru gjørdar av dulnevndum brúkarum frá eftirlitslistanum",
        "tog-watchlisthidepatrolled": "Fjal eftirhugdar broytingar frá eftirlitslistanum",
+       "tog-watchlisthidecategorization": "Fjal bólking av síðum",
        "tog-ccmeonemails": "Send mær avrit av teldubrøvum, sum eg sendi til aðrar brúkarar",
        "tog-diffonly": "Vís ikki innihaldið av síðuni undir broytingum",
        "tog-showhiddencats": "Vís goymdir bólkar",
        "morenotlisted": "Hesin listin er ikki liðugur.",
        "mypage": "Síða",
        "mytalk": "Kjak",
-       "anontalk": "Kjak til hesa IP-adressuna",
+       "anontalk": "Kjak",
        "navigation": "Navigatión",
        "and": "&#32;og",
        "qbfind": "Finn",
        "pool-timeout": "Støðgur, bíða verður eftir lásinum",
        "pool-queuefull": "Køin til \"hylin\" er full",
        "pool-errorunknown": "Ókend villa",
+       "pool-servererror": "Pool counter-tænastan er ikki tøk ($1).",
+       "poolcounter-usage-error": "Nýtslufeilur: $1",
        "aboutsite": "Um {{SITENAME}}",
        "aboutpage": "Project:Um",
        "copyright": "Innihaldið er tøkt undir $1, um ikki annað er viðmerkt.",
        "nstab-template": "Fyrimynd",
        "nstab-help": "Hjálp",
        "nstab-category": "Bólkur",
+       "mainpage-nstab": "Forsíða",
        "nosuchaction": "Ongin slík gerð",
        "nosuchactiontext": "Gerðin, ið tilskilað er í url, virkar ikki.\nMøguliga hevur tú stava urlin skeivt, ella fylgt einari skeivari leinkju.\nHetta kann eisini benda á ein feil í software'ini sum {{SITENAME}} brúkar.",
        "nosuchspecialpage": "Ongin slík serlig síða",
        "filerenameerror": "Kundi ikki umdoypa fílu \"$1\" til \"$2\".",
        "filedeleteerror": "Kundi ikki strika fíluna \"$1\".",
        "directorycreateerror": "Kundi ikki upprætta mappuna \"$1\".",
+       "directoryreadonlyerror": "Faldarin \"$1\" er vardur móti skriving.",
+       "directorynotreadableerror": "Faldarin \"$1\" er ikki lesbarur.",
        "filenotfound": "Kundi ikki finna fílu \"$1\".",
        "unexpected": "Óvæntað virði: \"$1\"=\"$2\".",
        "formerror": "Villa: Kundi ikki senda skránna.",
        "viewsource": "Vís keldu",
        "viewsource-title": "Sí keldu fyri $1",
        "actionthrottled": "Hendingin kvaldist",
-       "actionthrottledtext": "↓ Fyri at mótvirka spam, er tað ikki møguligt at gera hetta alt ov nógvar ferðir uppá stutta tíð, og tú ert farin yvir tað markið.\nVinarliga royn aftur um fáir minuttir.",
+       "actionthrottledtext": "Fyri at mótvirka spam, er tað ikki møguligt at gera hetta alt ov nógvar ferðir uppá stutta tíð, og tú ert farin yvir tað markið.\nVinarliga royn aftur um fáir minuttir.",
        "protectedpagetext": "Hendan síða er blivin vard fyri at steðga rættingum ella øðrum handlingum.",
-       "viewsourcetext": "Tú kanst síggja og avrita kelduna til hesa grein:",
-       "viewyourtext": "Tú kanst síggja og avrita kelduna fyri '''tínar rættingar''' til hesa síðuna:",
+       "viewsourcetext": "Tú kanst síggja og avrita kelduna til hesa grein.",
+       "viewyourtext": "Tú kanst síggja og avrita kelduna til <strong>tínar rættingar</strong> til hesa síðuna.",
        "protectedinterface": "↓ Henda síðan gevur markamóts tekst til ritbúnaðin (software), og er vard fyri at fyribyrgja misnýtslu.\nFyri at gera rættingar ella broyta týðingar á øllum wiki'um, vinarliga nýt [//translatewiki.net/ translatewiki.net], MediaWiki verkætlanina.",
-       "editinginterface": "↓ '''Ávaring:''' Tú rættar eina síðu sum verður brúkt til at geva markamóts tekst til ritbúnaðin (software).\nBroytingar á hesi síðu fara at ávirka útsjóndina á brúkara markamótinum (interface) fyri aðrar brúkarar á hesi wiki.\nFyri at gera týðingar ella broyta týðingar á øllum wiki, vinarliga nýt [//translatewiki.net/ translatewiki.net],  sum er ein MediaWiki verkætlan.",
-       "cascadeprotected": "Henda síðan er vard fyri rættingum, tí hon er í fylgjandi {{PLURAL:$1|síðu, sum er|síðum, sum eru}}\nvardar við \"arvaðari síðuverjing\"\n$2",
+       "editinginterface": "<strong>Ávaring:</strong> Tú rættar eina síðu sum verður brúkt til at geva markamóts tekst til ritbúnaðin (software).\nBroytingar á hesi síðu fara at ávirka útsjóndina á brúkara markamótinum (interface) fyri aðrar brúkarar á hesi wiki.",
+       "translateinterface": "Fyri at gera ella broyta týðingar fyri allar wikiir, vinarliga nýt [//translatewiki.net/ translatewiki.net], the MediaWiki staðsetingar verkætlan.",
+       "cascadeprotected": "Henda síðan er vard fyri rættingum, tí hon er í fylgjandi {{PLURAL:$1|síðu, sum er|síðum, sum eru}}\nvardar við \"niðurarvan\" møguleikanum tendraðum:\n$2",
        "namespaceprotected": "Tú hevur ikki loyvi til at rætta síður í $1 navnateiginum.",
        "customcssprotected": "Tú hevur ikki loyvi til at rætta hesa CSS síðuna, tí hon inniheldur persónligar innstillingar hjá øðrum brúkara.",
        "customjsprotected": "Tú hevur ikki loyvir til at rætta hesa JavaScript síðuna, tí hon inniheldur persónligar innstillingar hjá øðrum brúkara.",
        "mypreferencesprotected": "Tú hevur ikki loyvi til at rætta tínar preferensur.",
        "ns-specialprotected": "Serstakar síður kunnu ikki rættast.",
        "titleprotected": "[[User:$1|$1]] hevur vart hetta heitið frá skapan.\nGivin orsøk er <em>$2</em>.",
-       "filereadonlyerror": "Tað var ikki møguligt at broyta fíluna \"$1\" tí at fílugoymslan \"$2\" er í bara-lesa støðu.\n\nUmboðsstjórin sum stongdi hana, gav hesa frágreiðing: \"$3\".",
+       "filereadonlyerror": "Tað var ikki møguligt at broyta fíluna \"$1\" tí at fílugoymslan \"$2\" er skrivivard.\n\nSkipanaradministratorurin sum stongdi hana, gav hesa frágreiðing: \"$3\".",
        "invalidtitle-knownnamespace": "Ógyldugt heiti við navnaøki \"$2\" og teksti \"$3\"",
        "invalidtitle-unknownnamespace": "Ógyldigt heiti við ókendum navnaøkis tali $1 og teksti \"$2\"",
        "exception-nologin": "Tú ert ikki loggað/ur inn",
-       "exception-nologin-text": "Vinarliga [[Special:Userlogin|rita inn]] fyri at fáa atgongd til hesa síðu ella handling.",
+       "exception-nologin-text": "Vinarliga rita inn fyri at fáa atgongd til hesa síðu ella handling.",
        "exception-nologin-text-manual": "Vinarliga $1 fyri at fáa atgongd til hesa síðu ella handling.",
        "virus-badscanner": "Konfiguratións villa: Ókendur virus skannari: ''$1''",
        "virus-scanfailed": "↓  skanning virkaði ikki (kota $1)",
        "virus-unknownscanner": "ókent antivirus:",
        "logouttext": "'''Tú hevur nú ritað út.'''\n \nLegg til merkis, at summar síður framvegis vera vístar, sum um tú enn vart loggað/ur á, til tú hevur reinsað tín brovsara fyri \"cache\".",
+       "cannotlogoutnow-title": "Tað ber ikki til at rita út nú",
        "welcomeuser": "Vælkomin, $1!",
        "welcomecreation-msg": "Tín konta er nú stovnað.\nGloym ikki at broyta tínar [[Special:Preferences|{{SITENAME}}-innstillingar]].",
        "yourname": "Títt brúkaranavn:",
        "resetpass_submit": "Vel loyniorð og rita inn",
        "changepassword-success": "Títt loyniorð er nú broytt!",
        "changepassword-throttled": "Tú hevur roynt at rita inn ov nógvar ferðir nýliga.\nVinarliga bíða $1 áðrenn tú roynir aftur.",
+       "botpasswords-label-create": "Upprætta",
+       "botpasswords-label-update": "Dagfør",
+       "botpasswords-label-cancel": "Avbrót",
+       "botpasswords-label-delete": "Strika",
+       "botpasswords-label-resetpassword": "Nullstilla loyniorðið",
        "resetpass_forbidden": "Loyniorð kunnu ikki broytast",
        "resetpass-no-info": "Tú mást vera loggaður á fyri at fáa beinleiðis atgongd til hesa síðu.",
        "resetpass-submit-loggedin": "Broyt loyniorð",
        "sig_tip": "Tín undirskrift við tíðarstempli",
        "hr_tip": "Vatnrøtt linja (vera sparin við)",
        "summary": "Samandráttur:",
-       "subject": "Evni/heiti:",
+       "subject": "Evni:",
        "minoredit": "Hetta er smábroyting",
        "watchthis": "Hav eftirlit við hesi síðuni",
        "savearticle": "Goym síðu",
        "notextmatches": "Ongin síðutekstur samsvarar",
        "prevn": "undanfarnu {{PLURAL:$1|$1}}",
        "nextn": "næstu {{PLURAL:$1|$1}}",
+       "prev-page": "undanfarna síða",
        "next-page": "næsta síða",
        "prevn-title": "Gomul $1 {{PLURAL:$1|úrslit|úrslit}}",
        "nextn-title": "Næstu $1 {{PLURAL:$1|úrslit|úrslit}}",
        "search-redirect": "(umstilling $1)",
        "search-section": "(sektión $1)",
        "search-category": "(bólkur $1)",
+       "search-file-match": "(svarar til innihaldið av fíluni)",
        "search-suggest": "Meinti tú: $1",
+       "search-rewritten": "Vísir úrslit fyri $1. Leita í staðin eftir $2.",
        "search-interwiki-caption": "Líknandi verkætlanir",
        "search-interwiki-default": "Úrslit frá $1:",
        "search-interwiki-more": "(meira)",
        "showingresultsinrange": "Niðanfyri verða víst upp til {{PLURAL:$1|<strong>1</strong> úrslit|<strong>$1</strong> úrslit}} í økinum #<strong>$2</strong> til #<strong>$3</strong>.",
        "search-showingresults": "{{PLURAL:$4|Úrslit <strong>$1</strong> av <strong>$3</strong>|Úrslit <strong>$1 - $2</strong> av <strong>$3</strong>}}",
        "search-nonefound": "Leitingin gav onki úrslit.",
+       "search-nonefound-thiswiki": "Ongi úrslit passaðu til fyrispurningin á hesi síðu.",
        "powersearch-legend": "Víðkað leitan",
        "powersearch-ns": "Leita í navnaøkinum:",
        "powersearch-togglelabel": "Kanna eftir:",
        "preferences": "Innstillingar",
        "mypreferences": "Innstillingar",
        "prefs-edits": "Tal av rættingum:",
-       "prefsnologintext2": "Vinarliga $1 fyri at broyta tínar innstillingat.",
+       "prefsnologintext2": "Vinarliga rita inn fyri at broyta tínar innstillingar.",
        "prefs-skin": "Hamur",
        "skin-preview": "Forskoðan",
        "datedefault": "Ongi serlig ynskir",
        "prefs-personal": "Brúkaradáta",
        "prefs-rc": "Nýkomnar broytingar og stubbaskoðan",
        "prefs-watchlist": "Eftirlit",
+       "prefs-editwatchlist": "Rætta eftirlitslistan",
+       "prefs-editwatchlist-clear": "Rudda tín eftirlitslista",
        "prefs-watchlist-days": "Tal av døgum, sum skula vísast í eftirliti:",
        "prefs-watchlist-days-max": "Í mesta lagi $1 {{PLURAL:$1|dagur|dagar}}",
        "prefs-watchlist-edits": "Tal av rættingum, sum skula vísast í víðkaðum eftirliti:",
        "prefs-watchlist-token": "Lykil til eftirlitslistan:",
        "prefs-misc": "Ymiskar innstillingar",
        "prefs-resetpass": "Broyt loyniorð",
-       "prefs-changeemail": "Broyt t-post adressu",
+       "prefs-changeemail": "Broyt ella tak burtur t-post adressu",
        "prefs-setemail": "Skriva tína t-post adressu",
        "prefs-email": "T-post møguleikar",
        "prefs-rendering": "Útsjónd",
        "rows": "Røð:",
        "columns": "Teigar:",
        "searchresultshead": "Leita",
-       "stub-threshold": "Avmarkað til <a href=\"#\" class=\"stub\">stubba leinki</a> formatering (bytes):",
+       "stub-threshold": "Avmarkað til stubba leinki formatering $1:",
+       "stub-threshold-sample-link": "dømi",
        "stub-threshold-disabled": "Er gjørt óvirki",
        "recentchangesdays": "Dagar av vísa í seinastu broytingum:",
        "recentchangesdays-max": "Í mesta lagi $1 {{PLURAL:$1|dagur|dagar}}",
index d64d4db..a0ef809 100644 (file)
        "changecontentmodel-nodirectediting": "Le modèle de contenu $1 ne permet pas la modification directe",
        "log-name-contentmodel": "Journal de modification de modèle de contenu",
        "log-description-contentmodel": "Événements relatifs aux modèles de contenu d’une page",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|a créé}} la page $3 en utilisant un modèle de contenu « $5 » autre que celui par défaut",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|a modifié}} le modèle de contenu de la page $3 de « $4 » en « $5 »",
        "logentry-contentmodel-change-revertlink": "rétablir",
        "logentry-contentmodel-change-revert": "rétablir",
index cbdb0b0..97548fe 100644 (file)
        "category-empty": "<em>Ora cela catègoria contint gins de pâge de fichiér multimèdiâ.</em>",
        "hidden-categories": "{{PLURAL:$1|Catègoria cachiêe|Catègories cachiêes}}",
        "hidden-category-category": "Catègories cachiêes",
-       "category-subcat-count": "Cela catègoria-que at {{PLURAL:$2|mas que cela sot-catègoria.|{{PLURAL:$1|cela sot-catègoria|celes $1 sot-catègories}}, sur na soma de $2.}}",
-       "category-subcat-count-limited": "Cela catègoria-que at {{PLURAL:$1|cela sot-catègoria|celes $1 sot-catègories}}.",
-       "category-article-count": "{{PLURAL:$2|Cela catègoria-que contint mas que cela pâge.|{{PLURAL:$1|Cela pâge est|Celes $1 pâges sont}} a cela catègoria-que, sur na soma de $2.}}",
-       "category-article-count-limited": "{{PLURAL:$1|Cela pâge figure|Celes $1 pâges figuront}} dedens la presenta catègoria.",
-       "category-file-count": "{{PLURAL:$2|Cela catègoria-que contint mas que cél fichiér.|{{PLURAL:$1|Cél fichiér est|Celos $1 fichiérs sont}} a cela catègoria-que, sur na soma de $2.}}",
-       "category-file-count-limited": "{{PLURAL:$1|Cél fichiér figure|Celos $1 fichiérs figuront}} dedens la presenta catègoria.",
+       "category-subcat-count": "Cela catègoria at {{PLURAL:$2|mas que cela sot-catègoria-que.|{{PLURAL:$1|cela sot-catègoria|celes $1 sot-catègories}}-que, sur na soma de $2.}}",
+       "category-subcat-count-limited": "Cela catègoria at {{PLURAL:$1|cela sot-catègoria|celes $1 sot-catègories}}-que.",
+       "category-article-count": "{{PLURAL:$2|Cela catègoria contint mas que cela pâge-que.|{{PLURAL:$1|Cela pâge-que est|Celes $1 pâges-que sont}} a cela catègoria, sur na soma de $2.}}",
+       "category-article-count-limited": "{{PLURAL:$1|Cela pâge-que figure|Celes $1 pâges-que figuront}} dedens la presenta catègoria.",
+       "category-file-count": "{{PLURAL:$2|Cela catègoria contint mas que cél fichiér-que.|{{PLURAL:$1|Cél fichiér-que est|Celos $1 fichiérs-que sont}} a cela catègoria, sur na soma de $2.}}",
+       "category-file-count-limited": "{{PLURAL:$1|Cél fichiér-que figure|Celos $1 fichiérs-que figuront}} dedens la presenta catègoria.",
        "listingcontinuesabbrev": "(suita)",
        "index-category": "Pâges endèxâyes",
        "noindex-category": "Pâges pas endèxâyes",
        "title-invalid-magic-tilde": "Lo titro de la pâge demandâye contint na cobla de tildes magicos pas justa (<nowiki>~~~</nowiki>).",
        "title-invalid-too-long": "Lo titro de la pâge demandâye est trop long. Dêt pas dèpassar $1 octèt{{PLURAL:$1||s}} dens l’encodâjo UTF-8.",
        "title-invalid-leading-colon": "Lo titro de la pâge demandâye contint un doux-pouents pas justo u comencement.",
-       "perfcached": "Celes balyês sont en cacho et pôvont pas étre a jorn. Por lo més {{PLURAL:$1|un rèsultat est disponiblo|$1 rèsultats sont disponiblos}} dedens lo cacho.",
-       "perfcachedts": "Celes balyês sont en cacho et sont étâyes betâyes a jorn por lo dèrriér côp lo $1. Por lo més {{PLURAL:$4|un rèsultat est disponiblo|$4 rèsultats sont disponiblos}} dedens lo cacho.",
+       "perfcached": "Celes balyês-que sont en cacho et pôvont pas étre a jorn. Por lo més {{PLURAL:$1|un rèsultat est disponiblo|$1 rèsultats sont disponiblos}} dedens lo cacho.",
+       "perfcachedts": "Celes balyês-que sont en cacho et sont étâyes betâyes a jorn por lo dèrriér côp lo $1. Por lo més {{PLURAL:$4|un rèsultat est disponiblo|$4 rèsultats sont disponiblos}} dedens lo cacho.",
        "querypage-no-updates": "Ora les mêses a jorn por cela pâge sont dèsactivâyes.\nLes balyês ique seront pas betâyes a jorn.",
        "viewsource": "Vêre lo tèxto sôrsa",
        "viewsource-title": "Vêre lo tèxto sôrsa de $1",
        "protectedinterface": "Cela pâge-que balye de tèxto d’entèrface por la programeria sur cél vouiqui et est vêr protègiêe por èvitar los abus.\nPor apondre ou ben changiér de traduccions sur tôs los vouiquis, se vos plét empleyéd [//translatewiki.net/ translatewiki.net], lo projèt de localisacion de MediaWiki.",
        "editinginterface": "<strong>Atencion :</strong> vos éte aprés changiér na pâge empleyêe por fâre lo tèxto d’entèrface de la programeria.\nLos changements sè cognetront sur l’aparence de l’entèrface utilisator por los ôtros utilisators de cél vouiqui.",
        "translateinterface": "Por apondre ou ben changiér des traduccions sur tôs los vouiquis, se vos plét empleyéd [//translatewiki.net/ translatewiki.net], lo projèt de localisacion de MediaWiki.",
-       "cascadeprotected": "Cela pâge-que est protègiêe contre los changements perce qu’el est transcllua per {{PLURAL:$1|cela pâge qu’est étâye protègiêe|celes pâges que sont étâyes protègiêes}} avouéc lo chouèx « protèccion en cascâda » activâ :\n$2",
+       "cascadeprotected": "Cela pâge est protègiêe contre los changements perce qu’el est transcllua per {{PLURAL:$1|cela pâge-que qu’est étâye protègiêe|celes pâges-que que sont étâyes protègiêes}} avouéc lo chouèx « protèccion en cascâda » activâ :\n$2",
        "namespaceprotected": "Vos éd pas la pèrmission de changiér les pâges de l’èspâço de noms « <strong>$1</strong> ».",
        "customcssprotected": "Vos éd pas la pèrmission de changiér cela pâge CSS perce que contint la configuracion a sè d’un ôtr’utilisator.",
        "customjsprotected": "Vos éd pas la pèrmission de changiér cela pâge JavaScript perce que contint la configuracion a sè d’un ôtr’utilisator.",
        "mailerror": "Fôta pendent l’èxpèdicion du mèssâjo : $1",
        "acct_creation_throttle_hit": "Des vesitors de cél vouiqui-que qu’emplèyont voutron adrèce IP ant fêt $1 comptio{{PLURAL:$1||s}} pendent lo jorn passâ, cen qu’est lo més ôtorisâ dens ceti temps.\nDu côp los vesitors qu’emplèyont cel’adrèce IP pôvont fâre gins de comptio por lo moment.",
        "emailauthenticated": "Voutron adrèce èlèctronica est étâye confirmâye lo $2 a $3.",
-       "emailnotauthenticated": "Voutron adrèce èlèctronica est p’oncor confirmâye.\nNion mèssâjo serat mandâ por châcuna de celes fonccionalitâts.",
+       "emailnotauthenticated": "Voutron adrèce èlèctronica est p’oncor confirmâye.\nNion mèssâjo serat mandâ por châcuna de celes fonccionalitâts-que.",
        "noemailprefs": "Spècifiâd un’adrèce èlèctronica dens voutres prèferences por empleyér celes fonccionalitâts.",
        "emailconfirmlink": "Confirmâd voutron adrèce èlèctronica",
        "invalidemailaddress": "Cel’adrèce èlèctronica pôt pas étre accèptâye perce que semble avêr un format pas justo.\nSe vos plét, buchiéd un’adrèce ben formatâye ou ben lèssiéd cél champ vouedo.",
        "createaccount-text": "Yon at fêt un comptio por voutron adrèce èlèctronica dessus {{SITENAME}} ($4) apelâ « $2 », avouéc lo contresegno « $3 ».\nVos vos devriâd branchiér et pués changiér dês ora voutron contresegno.\n\nIgnorâd cél mèssâjo-que se cél compto est étâ fêt per fôta.",
        "login-throttled": "Dês pou vos éd èprovâ un mouél de branchements.\nSe vos plét, atende-vos $1 devant que tornar èprovar.",
        "login-abort-generic": "Voutron branchement at pas reussi - Anulâ",
-       "login-migrated-generic": "Voutron comptio est étâ migrâ, et pués voutron nom d’utilisator ègziste pas més dessus cél vouiqui.",
+       "login-migrated-generic": "Voutron comptio est étâ migrâ, et pués voutron nom d’utilisator ègziste pas més sur cél vouiqui.",
        "loginlanguagelabel": "Lengoua : $1",
        "suspicious-userlogout": "Voutra demanda de dèbranchement est étâye refusâye perce que semble qu’est étâye mandâye per un navegator câsso ou ben la mêsa en cacho d’un proxi.",
        "createacct-another-realname-tip": "Lo veré nom est u chouèx.\nSe vos dècidâd de lo balyér, serat empleyê por atribuar a l’utilisator ses ôvres.",
        "changeemail-throttled": "Vos éd èprovâ un mouél de branchements.\nSe vos plét, atende-vos $1 devant que tornar èprovar.",
        "changeemail-nochange": "Se vos plét, buchiéd na novèl’adrèce èlèctronica difèrenta.",
        "resettokens": "Rebetar a zérô los jetons",
-       "resettokens-text": "Ique, vos pouede rebetar a zérô los jetons que pèrmètont d’arrevar a quârques balyês privâyes associyêes a voutron comptio.\n\nVos o devriâd fâre se vos los éd partagiês per accident avouéc yon ou ben se voutron comptio est étâ compromètu.",
+       "resettokens-text": "Ique, vos pouede rebetar a zérô los jetons que pèrmètont d’arrevar a quârques balyês privâyes associyêes a voutron comptio.\n\nVos o devriâd fâre se vos los éd partagiês per accident avouéc yon ou ben se voutron comptio est étâ comprometu.",
        "resettokens-no-tokens": "Y at gins de jeton a rebetar a zérô.",
        "resettokens-tokens": "Jetons :",
        "resettokens-token-label": "$1 (valor d’ora : $2)",
        "loginreqlink": "branchiér",
        "loginreqpagetext": "Se vos plét, vos vos dête $1 por povêr vêre les ôtres pâges.",
        "accmailtitle": "Contresegno mandâ",
-       "accmailtext": "Un contresegno fêt a l’hasârd por [[User talk:$1|$1]] est étâ mandâ a $2. Pôt étre changiê sur la pâge de <em>[[Special:ChangePassword|changement de contresegno]]</em> aprés branchement.",
+       "accmailtext": "Un contresegno fêt a l’hasârd por [[User talk:$1|$1]] est étâ mandâ a $2. Pôt étre changiê dessus la pâge de <em>[[Special:ChangePassword|changement de contresegno]]</em> aprés branchement.",
        "newarticle": "(Novél)",
-       "newarticletext": "Vos éd siuvu un lim de vers na pâge qu’ègziste p’oncor.\nPor fâre cela pâge, buchiéd voutron tèxto dedens la bouèta ce-desot (vêde la [$1 pâge d’éde] por més d’enformacions).\nSe vos éte arrevâ{{GENDER:||ye}} ique per fôta, cllicâd sur lo boton <strong>Devant</strong> de voutron navegator.",
+       "newarticletext": "Vos éd siuvu un lim de vers na pâge qu’ègziste p’oncor.\nPor fâre cela pâge, buchiéd voutron tèxto dedens la bouèta ce-desot (vêde la [$1 pâge d’éde] por més d’enformacions).\nSe vos éte arrevâ{{GENDER:||ye}} ique per fôta, cllicâd dessus lo boton <strong>Devant</strong> de voutron navegator.",
        "anontalkpagetext": "----\n<em>O est la pâge de discussion d’un utilisator anonimo qu’at p’oncor fêt un comptio ou ben que nen emplèye pas.</em>\nPor cen, nos devens empleyér son adrèce IP numerica por o identifiar.\nUn’adrèce IP d’ense pôt étre partagiêe per un mouél d’utilisators.\nSe vos éte {{GENDER:|un utilisator|un’utilisatrice}} anonim{{GENDER:|o|a}} et pués se vos constatâd que des comentèros que vos regârdont pas vos sont étâs adrèciês, se vos plét [[Special:UserLogin/signup|féte un comptio]] ou ben [[Special:UserLogin|branchiéd-vos]] por èvitar tota confusion que vint avouéc d’ôtros utilisators anonimos.",
        "noarticletext": "Ora y at gins de tèxto dedens cela pâge.\nVos pouede [[Special:Search/{{PAGENAME}}|fâre na rechèrche sur cél titro]] dedens les ôtres pâges,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechèrchiér dedens los jornâls liyês]\nou ben [{{fullurl:{{FULLPAGENAME}}|action=edit}} fâre cela pâge]</span>.",
        "noarticletext-nopermission": "Ora y at gins de tèxto dedens cela pâge.\nVos pouede [[Special:Search/{{PAGENAME}}|fâre na rechèrche sur cél titro]] dedens les ôtres pâges ou ben <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechèrchiér dedens los jornâls liyês]</span>, mas vos éd pas la pèrmission de fâre cela pâge.",
-       "missing-revision": "La vèrsion n° $1 de la pâge apelâye « {{FULLPAGENAME}} » ègziste pas.\n\nEn g·ènèrâl cen arreve en siuvent un lim dèpassâ d’un historico de vers na pâge qu’est étâye suprimâye.\nVos pouede trovar més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
+       "missing-revision": "La vèrsion n° $1 de la pâge apelâye « {{FULLPAGENAME}} » ègziste pas.\n\nEn g·ènèrâl cen arreve en siuvent un lim dèpassâ d’un historico de vers na pâge qu’est étâye suprimâye.\nVos pouede trovar més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
        "userpage-userdoesnotexist": "Lo comptio utilisator « $1 » est pas encartâ.\nSe vos plét, controlâd se vos voléd fâre ou ben changiér cela pâge.",
        "userpage-userdoesnotexist-view": "Lo comptio utilisator « $1 » est pas encartâ.",
        "blocked-notice-logextract": "Ora {{GENDER:$1|cél utilisator|cel’utilisatrice}} est blocâ{{GENDER:$1||ye}}.\nLa dèrriére entrâ du jornâl des blocâjos est balyêe ce-desot coment rèference :",
        "sectioneditnotsupported-title": "Changement de sèccion pas recognu",
        "sectioneditnotsupported-text": "Lo changement d’una sèccion est pas recognu dens cela pâge.",
        "permissionserrors": "Fôta de pèrmission",
-       "permissionserrorstext": "Vos éd pas la pèrmission de fâre l’accion demandâye por {{PLURAL:$1|cela rêson|celes rêsons}} :",
-       "permissionserrorstext-withaction": "Vos éd pas la pèrmission de $2 por {{PLURAL:$1|cela rêson|celes rêsons}} :",
+       "permissionserrorstext": "Vos éd pas la pèrmission de fâre l’accion demandâye por {{PLURAL:$1|cela rêson|celes rêsons}}-que :",
+       "permissionserrorstext-withaction": "Vos éd pas la pèrmission de $2 por {{PLURAL:$1|cela rêson|celes rêsons}}-que :",
        "contentmodelediterror": "Vos pouede pas changiér cela vèrsion perce que son modèlo de contegnu est <code>$1</code>, cen que sè difèrence du modèlo de contegnu d’ora de la pâge <code>$2</code>.",
        "recreate-moveddeleted-warn": "<strong>Atencion : vos éte aprés refâre na pâge qu’est étâye suprimâye dês devant.</strong>\n\nDemandâd-vos se fôt franc continuar son changement.\nLo jornâl de les suprèssions et des dèplacements de cela pâge est balyê ce-desot por comoditât :",
        "moveddeleted-notice": "Cela pâge est étâye suprimâye.\nLo jornâl de les suprèssions et des dèplacements de cela pâge est balyê ce-desot coment rèference.",
        "defaultmessagetext": "Mèssâjo per dèfôt",
        "content-failed-to-parse": "Falyita de l’analise du contegnu de $2 por lo modèlo $1 : $3",
        "invalid-content-data": "Balyês du contegnu pas justes",
-       "content-not-allowed-here": "Lo contegnu « $1 » est pas ôtorisâ sur la pâge [[$2]]",
+       "content-not-allowed-here": "Lo contegnu « $1 » est pas ôtorisâ dessus la pâge [[$2]]",
        "editwarning-warning": "Quitar cela pâge vos farat pèrdre tôs los changements que vos éd fêts.\nSe vos éte branchiê{{GENDER:||e}}, vos pouede dèsactivar cela semonce dedens la sèccion « {{int:prefs-editing}} » de voutres prèferences.",
        "editpage-notsupportedcontentformat-title": "Format de contegnu pas recognu",
        "editpage-notsupportedcontentformat-text": "Lo format de contegnu $1 est pas recognu per lo modèlo de contegnu $2.",
        "rev-deleted-user": "(nom d’utilisator enlevâ)",
        "rev-deleted-event": "(dètalys du jornâl enlevâs)",
        "rev-deleted-user-contribs": "[nom d’utilisator ou ben adrèce IP enlevâ(ye) - changement cachiê sur les contribucions]",
-       "rev-deleted-text-permission": "Cela vèrsion de la pâge est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
-       "rev-suppressed-text-permission": "Cela vèrsion de la pâge est étâye <strong>rèprimâye</strong>.\nY pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].",
-       "rev-deleted-text-unhide": "Cela vèrsion de la pâge est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].\nVos pouede adés [$1 vêre cela vèrsion] se vos o voléd.",
-       "rev-suppressed-text-unhide": "Cela vèrsion de la pâge est étâye <strong>rèprimâye</strong>.\nY pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].\nVos pouede adés [$1 vêre cela vèrsion] se vos o voléd.",
-       "rev-deleted-text-view": "Cela vèrsion de la pâge est étâye <strong>suprimâye</strong>.\nVos la pouede vêre ; y pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
-       "rev-suppressed-text-view": "Cela vèrsion de la pâge est étâye <strong>rèprimâye</strong>.\nVos la pouede vêre ; y pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].",
-       "rev-deleted-no-diff": "Vos pouede pas vêre cela dif perce que yona de les vèrsions est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
+       "rev-deleted-text-permission": "Cela vèrsion de la pâge est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
+       "rev-suppressed-text-permission": "Cela vèrsion de la pâge est étâye <strong>rèprimâye</strong>.\nY pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].",
+       "rev-deleted-text-unhide": "Cela vèrsion de la pâge est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].\nVos pouede adés [$1 vêre cela vèrsion] se vos o voléd.",
+       "rev-suppressed-text-unhide": "Cela vèrsion de la pâge est étâye <strong>rèprimâye</strong>.\nY pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].\nVos pouede adés [$1 vêre cela vèrsion] se vos o voléd.",
+       "rev-deleted-text-view": "Cela vèrsion de la pâge est étâye <strong>suprimâye</strong>.\nVos la pouede vêre ; y pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
+       "rev-suppressed-text-view": "Cela vèrsion de la pâge est étâye <strong>rèprimâye</strong>.\nVos la pouede vêre ; y pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].",
+       "rev-deleted-no-diff": "Vos pouede pas vêre cela dif perce que yona de les vèrsions est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
        "rev-suppressed-no-diff": "Vos pouede pas vêre cela dif perce que yona de les vèrsions est étâye <strong>suprimâye</strong>.",
-       "rev-deleted-unhide-diff": "Yona de les vèrsions de cela dif est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].\nVos pouede adés [$1 vêre cela dif] se vos o voléd.",
-       "rev-suppressed-unhide-diff": "Yona de les vèrsions de cela dif est étâye <strong>rèprimâye</strong>.\nY pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].\nVos pouede adés [$1 vêre cela dif] se vos o voléd.",
-       "rev-deleted-diff-view": "Yona de les vèrsions de cela dif est étâye <strong>suprimâye</strong>.\nVos pouede vêre cela dif ; y pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
-       "rev-suppressed-diff-view": "Yona de les vèrsions de cela dif est étâye <strong>rèprimâye</strong>.\nVos pouede vêre cela dif ; y pôt avêr més de dètalys sur lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].",
+       "rev-deleted-unhide-diff": "Yona de les vèrsions de cela dif est étâye <strong>suprimâye</strong>.\nY pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].\nVos pouede adés [$1 vêre cela dif] se vos o voléd.",
+       "rev-suppressed-unhide-diff": "Yona de les vèrsions de cela dif est étâye <strong>rèprimâye</strong>.\nY pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].\nVos pouede adés [$1 vêre cela dif] se vos o voléd.",
+       "rev-deleted-diff-view": "Yona de les vèrsions de cela dif est étâye <strong>suprimâye</strong>.\nVos pouede vêre cela dif ; y pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
+       "rev-suppressed-diff-view": "Yona de les vèrsions de cela dif est étâye <strong>rèprimâye</strong>.\nVos pouede vêre cela dif ; y pôt avêr més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} jornâl de les rèprèssions].",
        "rev-delundel": "changiér la visibilitât",
        "rev-showdeleted": "montrar",
        "revisiondelete": "Suprimar ou ben refâre de vèrsions",
        "logdelete-text": "Los èvènements du jornâl suprimâs continueront a aparêtre dedens los jornâls, mas na partia de lor contegnu serat inaccèssibla u publico.",
        "revdelete-text-others": "Los ôtros administrators porront adés arrevar u contegnu cachiê et lo refâre, a muens que des rèstriccions de més seyont dèfenies.",
        "revdelete-confirm": "Se vos plét, confirmâd qu’o est franc cen que vos voléd fâre, que vos en compregnéd les consèquences, et pués que vos o féte en acôrd avouéc les [[{{MediaWiki:Policy-url}}|règlles]].",
-       "revdelete-suppress-text": "La rèprèssion dêt étre empleyêe <strong>ren que</strong> dens celos câs :\n* Enformacions que pôvont étre difamatouères\n* Enformacions a sè que vant pas avouéc\n*: <em>adrèce, numerô de tèlèfono, numerô d’identificacion nacionâl, et tot cen que vat avouéc</em>",
+       "revdelete-suppress-text": "La rèprèssion dêt étre empleyêe <strong>ren que</strong> dens celos câs-que :\n* Enformacions que pôvont étre difamatouères\n* Enformacions a sè que vant pas avouéc\n*: <em>adrèce, numerô de tèlèfono, numerô d’identificacion nacionâl, et tot cen que vat avouéc</em>",
        "revdelete-legend": "Dèfenir de rèstriccions de visibilitât",
        "revdelete-hide-text": "Tèxto de la vèrsion",
        "revdelete-hide-image": "Cachiér lo contegnu du fichiér",
        "revdelete-modify-missing": "Fôta en changient la piéce avouéc l’ID $1 : el est manquenta dedens la bâsa de balyês !",
        "revdelete-no-change": "<strong>Atencion :</strong> la piéce datâye du $1 a $2 at ja la configuracion de visibilitât demandâye.",
        "revdelete-concurrent-change": "Fôta en changient la piéce datâye du $1 a $2 : son statut semble étre étâ changiê per un ôtro justo que vos èprovâvâd d’o changiér.\nSe vos plét, controlâd los jornâls.",
-       "revdelete-only-restricted": "Fôta en cachient la piéce datâye du $1 a $2 : vos pouede pas rèprimar de piéces de la viua ux administrators sen chouèsir avouéc des ôtros chouèx de visibilitât.",
+       "revdelete-only-restricted": "Fôta en cachient la piéce datâye du $1 a $2 : vos pouede pas rèprimar de piéces de la viua ux administrators sen chouèsir avouéc dôtros chouèx de visibilitât.",
        "revdelete-reason-dropdown": "*Rêsons corentes de suprèssion\n** Violacion du drêt d’ôtor\n** Comentèros enformacions a sè que vant pas avouéc\n** Nom d’utilisator que vat pas avouéc\n** Enformacions que pôvont étre difamatouères",
        "revdelete-otherreason": "Ôtra rêson ou ben rêson de més :",
        "revdelete-reasonotherlist": "Ôtra rêson",
        "mergehistory-from": "Pâge d’origina :",
        "mergehistory-into": "Pâge de dèstinacion :",
        "mergehistory-list": "Historico des changements que pôvont étre fusionâs",
-       "mergehistory-merge": "Celes vèrsions de [[:$1]] pôvont étre fusionâyes dedens [[:$2]].\nEmpleyéd la colona de botons de chouèx por fusionar mas que les vèrsions fêtes dês lo comencement tant qu’a la dâta spècifiâye.\nNotâd que l’usâjo des lims de navegacion rebeterat a zérô cela colona.",
+       "mergehistory-merge": "Celes vèrsions-que de [[:$1]] pôvont étre fusionâyes dedens [[:$2]].\nEmpleyéd la colona de botons de chouèx por fusionar mas que les vèrsions fêtes dês lo comencement tant qu’a la dâta spècifiâye.\nNotâd que l’usâjo des lims de navegacion rebeterat a zérô cela colona.",
        "mergehistory-go": "Montrar los changements que pôvont étre fusionâs",
        "mergehistory-submit": "Fusionar les vèrsions",
        "mergehistory-empty": "Niona vèrsion pôt étre fusionâye.",
        "diff-multi-sameuser": "({{PLURAL:$1|Na vèrsion entèrmèdièra per lo mém’utilisator pas montrâye|$1 vèrsions entèrmèdières per lo mém’utilisator pas montrâyes}})",
        "diff-multi-otherusers": "({{PLURAL:$1|Na vèrsion entèrmèdièra|$1 vèrsions entèrmèdières}} per {{PLURAL:$2|un ôtr’utilisator|$2 utilisators}} pas montrâye{{PLURAL:$1||s}})",
        "diff-multi-manyusers": "({{PLURAL:$1|Na vèrsion entèrmèdièra|$1 vèrsions entèrmèdières}} per més {{PLURAL:$2|d’un utilisator|de $2 utilisators}} {{PLURAL:$1|est pas montrâye|sont pas montrâyes}})",
-       "difference-missing-revision": "{{PLURAL:$2|Na vèrsion|$2 vèrsions}} de cela difèrence ($1) {{PLURAL:$2|est pas étâye trovâye|sont pas étâyes trovâyes}}.\n\nEn g·ènèrâl cen arreve en siuvent un lim dèpassâ d’una dif de vers na pâge qu’est étâye suprimâye.\nVos pouede trovar més de dètalys sur lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
+       "difference-missing-revision": "{{PLURAL:$2|Na vèrsion|$2 vèrsions}} de cela difèrence ($1) {{PLURAL:$2|est pas étâye trovâye|sont pas étâyes trovâyes}}.\n\nEn g·ènèrâl cen arreve en siuvent un lim dèpassâ d’una dif de vers na pâge qu’est étâye suprimâye.\nVos pouede trovar més de dètalys dessus lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornâl de les suprèssions].",
        "searchresults": "Rèsultats de la rechèrche",
        "searchresults-title": "Rèsultats de la rechèrche por « $1 »",
        "titlematches": "Corrèspondances dedens los titros de les pâges",
        "shown-title": "Montrar $1 rèsultat{{PLURAL:$1||s}} per pâge",
        "viewprevnext": "Vêre ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "<strong>Y at na pâge apelâye « [[:$1]] » sur cél vouiqui.</strong> {{PLURAL:$2|0=|Vêde avouéc los ôtros rèsultats de voutra rechèrche.}}",
-       "searchmenu-new": "<strong>Fâre la pâge « [[:$1|$1]] » sur cél vouiqui !</strong> {{PLURAL:$2|0=|Vêde avouéc la pâge trovâye avouéc voutra rechèrche.|Vêde avouéc los rèsultats de voutra rechèrche.}}",
+       "searchmenu-new": "<strong>Fâre la pâge « [[:$1|$1]] » sur cél vouiqui !</strong> {{PLURAL:$2|0=|Vêde asse-ben la pâge trovâye avouéc voutra rechèrche.|Vêde avouéc los rèsultats de voutra rechèrche.}}",
        "searchprofile-articles": "Pâges de contegnu",
        "searchprofile-images": "Multimèdiâ",
        "searchprofile-everything": "Tot",
        "yourvariant": "Vèrsion de la lengoua du contegnu :",
        "prefs-help-variant": "Voutra vèrsion voutron ortografia prèferâye por fâre vêre les pâges de contegnu de cél vouiqui.",
        "yournick": "Signatura novèla :",
-       "prefs-help-signature": "Los comentèros sur les pâges de discussion dêvont étre signês avouéc « <nowiki>~~~~</nowiki> » que serat convèrti per voutra signatura et un horodatâjo.",
+       "prefs-help-signature": "Los comentèros dessus les pâges de discussion dêvont étre signês avouéc « <nowiki>~~~~</nowiki> » que serat convèrti per voutra signatura et un horodatâjo.",
        "badsig": "Signatura bruta pas justa.\nControlâd les balises HTML.",
        "badsiglength": "Voutra signatura est trop longe.\nDêt pas dèpassar $1 caractèro{{PLURAL:$1||s}}.",
        "yourgender": "Coment vos prèferâd étre dècrit ?",
        "email": "Mèssageria èlèctronica",
        "prefs-help-realname": "Lo veré nom est u chouèx.\nS’il est balyê, serat empleyê por vos atribuar voutres ôvres.",
        "prefs-help-email": "L’adrèce èlèctronica est u chouèx, mas el est nècèssèra por rebetar a zérô voutron contresegno, se vos vegnévâd a l’oubliar.",
-       "prefs-help-email-others": "Vos porriâd avouéc chouèsir de lèssiér los ôtros lor veriér vers vos per mèssageria èlèctronica avouéc un lim sur voutra pâge utilisator ou ben de discussion sen que seye nècèssèro de rèvèlar voutron adrèce èlèctronica.",
+       "prefs-help-email-others": "Vos porriâd asse-ben chouèsir de lèssiér los ôtros lor veriér vers vos per mèssageria èlèctronica avouéc un lim sur voutra pâge utilisator ou ben de discussion sen que seye nècèssèro de rèvèlar voutron adrèce èlèctronica.",
        "prefs-help-email-required": "Un’adrèce èlèctronica est nècèssèra.",
        "prefs-info": "Enformacions de bâsa",
        "prefs-i18n": "Entèrnacionalisacion",
        "right-purge": "Purgiér lo cacho du seto d’una pâge sen confirmacion",
        "right-autoconfirmed": "Pas étre afèctâ per les limitacions de dèbit liyêes a les adrèces IP",
        "right-bot": "Étre trètâ coment un mècanismo ôtomatisâ",
-       "right-nominornewtalk": "Pas dècllenchiér la notificacion de mèssâjo novél quand fant un petiôt changement sur la pâge de discussion d’un utilisator",
+       "right-nominornewtalk": "Pas dècllenchiér la notificacion de mèssâjo novél quand fant un petiôt changement dessus la pâge de discussion d’un utilisator",
        "right-apihighlimits": "Empleyér de limites ples hôtes dedens les demandes API",
        "right-writeapi": "Empleyér l’API d’ècritura",
        "right-delete": "Suprimar de pâges",
        "upload_directory_read_only": "Lo rèpèrtouèro de tèlèchargement ($1) est pas accèssiblo en ècritura dês lo sèrvior Vouèbe.",
        "uploaderror": "Fôta pendent lo tèlèchargement",
        "upload-recreate-warning": "<strong>Atencion : un fichiér avouéc cél nom est étâ suprimâ ou ben dèplaciê.</strong>\n\nLo jornâl de les suprèssions et des dèplacements de cela pâge est balyê ique por comoditât :",
-       "uploadtext": "Empleyéd lo formulèro ce-desot por tèlèchargiér de fichiérs.\nPor vêre ou ben rechèrchiér de fichiérs tèlèchargiês dês devant, vêde la [[Special:FileList|lista des fichiérs tèlèchargiês]]. Los (re-)tèlèchargements sont asse-ben encartâs sur lo [[Special:Log/upload|jornâl des tèlèchargements]], et les suprèssions sur lo [[Special:Log/delete|jornâl de les suprèssions]].\n\nPor entrebetar un fichiér dedens na pâge, empleyéd un lim de yona de celes fôrmes :\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.jpg]]</nowiki></code></strong> por empleyér la vèrsion en plêna largior du fichiér\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.png|200px|thumb|left|tèxto dèscriptif]]</nowiki></code></strong> por empleyér na miniatura de 200 pixèls de lârjo dedens na bouèt’a gôche avouéc « tèxto dèscriptif » coment dèscripcion\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Fichiér.ogg]]</nowiki></code></strong> por liyér tot drêt vers lo fichiér sen lo fâre vêre",
+       "uploadtext": "Empleyéd lo formulèro ce-desot por tèlèchargiér de fichiérs.\nPor vêre ou ben rechèrchiér de fichiérs tèlèchargiês dês devant, vêde la [[Special:FileList|lista des fichiérs tèlèchargiês]]. Los (re-)tèlèchargements sont asse-ben encartâs dessus lo [[Special:Log/upload|jornâl des tèlèchargements]], et les suprèssions dessus lo [[Special:Log/delete|jornâl de les suprèssions]].\n\nPor entrebetar un fichiér dedens na pâge, empleyéd un lim de yona de celes fôrmes-que :\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.jpg]]</nowiki></code></strong> por empleyér la vèrsion en plêna largior du fichiér\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Fichiér.png|200px|thumb|left|tèxto dèscriptif]]</nowiki></code></strong> por empleyér na miniatura de 200 pixèls de lârjo dedens na bouèt’a gôche avouéc « tèxto dèscriptif » coment dèscripcion\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Fichiér.ogg]]</nowiki></code></strong> por liyér tot drêt vers lo fichiér sen lo fâre vêre",
        "upload-permitted": "Tipo{{PLURAL:$2||s}} de fichiérs ôtorisâ{{PLURAL:$2||s}} : $1.",
        "upload-preferred": "Tipo{{PLURAL:$2||s}} de fichiérs prèferâ{{PLURAL:$2||s}} : $1.",
        "upload-prohibited": "Tipo{{PLURAL:$2||s}} de fichiérs dèfendu{{PLURAL:$2||s}} : $1.",
        "emptyfile": "Lo fichiér que vos éd tèlèchargiê semble étre vouedo.\nCen pôt étre diu a na fôta dedens lo nom du fichiér.\nSe vos plét, controlâd que vos voléd franc tèlèchargiér cél fichiér.",
        "windows-nonascii-filename": "Cél vouiqui recognêt pas los noms de fichiérs avouéc des caractèros spèciâls.",
        "fileexists": "Un fichiér avouéc cél nom ègziste ja, se vos plét controlâd <strong>[[:$1]]</strong> se {{GENDER:|vos}} éte pas de sûr de lo volêr changiér.\n[[$1|thumb]]",
-       "filepageexists": "La pâge de dèscripcion por cél fichiér est ja étâye fêta ique <strong>[[:$1]]</strong>, mas nion fichiér avouéc cél nom ègziste ora.\nLo rèsumâ que vos voléd buchiér aparêtrat pas sur la pâge de dèscripcion.\nPor o fâre, vos la devréd changiér a la man.\n[[$1|thumb]]",
+       "filepageexists": "La pâge de dèscripcion por cél fichiér est ja étâye fêta ique <strong>[[:$1]]</strong>, mas nion fichiér avouéc cél nom ègziste ora.\nLo rèsumâ que vos voléd buchiér aparêtrat pas dessus la pâge de dèscripcion.\nPor o fâre, vos la devréd changiér a la man.\n[[$1|thumb]]",
        "fileexists-extension": "Un fichiér avouéc un nom d’ense ègziste ja : [[$2|thumb]]\n* Nom du fichiér a tèlèchargiér : <strong>[[:$1]]</strong>\n* Nom du fichiér ègzistent : <strong>[[:$2]]</strong>\nPôt-étre vos voléd empleyér un nom ples prôpro ?",
        "fileexists-thumbnail-yes": "Lo fichiér semble étre un’émâge en talye rèduita <em>(miniatura)</em>.\n[[$1|thumb]]\nSe vos plét, controlâd lo fichiér <strong>[[:$1]]</strong>.\nSe lo fichiér controlâ est la mém’émâge avouéc la talye originâla, y at pas fôta de tèlèchargiér na miniatura de més.",
        "file-thumbnail-no": "Lo nom du fichiér comence per <strong>$1</strong>.\nSemble étre un’émâge en talye rèduita <em>(miniatura)</em>.\nSe vos éd cel’émâge en hôta rèsolucion, tèlèchargiéd-la, ôtrament changiéd son nom, se vos plét.",
        "foreign-structured-upload-form-label-own-work": "Su l’ôtor de cel’ôvra",
        "foreign-structured-upload-form-label-infoform-categories": "Catègories",
        "foreign-structured-upload-form-label-infoform-date": "Dâta",
-       "foreign-structured-upload-form-label-own-work-message-local": "Confirmo que tèlèchârjo cél fichiér d’aprés les condicions et les politiques de licence de {{SITENAME}}.",
+       "foreign-structured-upload-form-label-own-work-message-local": "Confirmo que tèlèchârjo cél fichiér d’aprés les condicions d’usâjo et les politiques de licence de {{SITENAME}}.",
        "foreign-structured-upload-form-label-not-own-work-message-local": "Se vos pouede pas tèlèchargiér cél fichiér d’aprés les politiques de {{SITENAME}}, se vos plét cllôde cela bouèta de dialogo et pués èprovâd un’ôtra mètoda.",
-       "foreign-structured-upload-form-label-not-own-work-local-local": "Vos pouede avouéc èprovar la [[Special:Upload|pâge de tèlèchargement per dèfôt]].",
+       "foreign-structured-upload-form-label-not-own-work-local-local": "Vos pouede asse-ben èprovar la [[Special:Upload|pâge de tèlèchargement per dèfôt]].",
+       "foreign-structured-upload-form-label-own-work-message-default": "Compregno que tèlèchârjo cél fichiér vers un dèpôt partagiê. Confirmo qu’o fé d’aprés les condicions d’usâjo et les politiques de licence de ceti.",
+       "foreign-structured-upload-form-label-not-own-work-message-default": "Se vos pouede pas tèlèchargiér cél fichiér d’aprés les politiques du dèpôt partagiê, se vos plét cllôde cela bouèta de dialogo et pués èprovâd un’ôtra mètoda.",
+       "foreign-structured-upload-form-label-not-own-work-local-default": "Vos pouede asse-ben èprovar d’empleyér la [[Special:Upload|pâge de tèlèchargement de {{SITENAME}}]], se cél fichiér y pôt étre tèlèchargiê d’aprés lors politiques.",
+       "foreign-structured-upload-form-label-own-work-message-shared": "Cèrtifio étre lo dètentior des drêts d’ôtor sur cél fichiér, et j’accèpto de publeyér cél fichiér dessus Wikimedia Commons en lo betent irrèvocâblament desot licence [https://creativecommons.org/licenses/by-sa/4.0/ Creative Commons Atribucion - Partâjo dens les Mémes Condicions 4.0] et pués j’accèpto les [https://wikimediafoundation.org/wiki/Terms_of_Use condicions d’usâjo].",
+       "foreign-structured-upload-form-label-not-own-work-message-shared": "Se vos éte pas lo dètentior des drêts d’ôtor sur cél fichiér ou ben que vos lo voléd publeyér desot na licence difèrenta, vos pouede empleyér l’[https://commons.wikimedia.org/wiki/Special:UploadWizard assistent de tèlèchargement de Commons].",
+       "foreign-structured-upload-form-label-not-own-work-local-shared": "Vos pouede asse-ben èprovar d’empleyér la [[Special:Upload|pâge de tèlèchargement de {{SITENAME}}]], se cél fichiér y pôt étre tèlèchargiê d’aprés lors politiques.",
        "backend-fail-stream": "Y at pas moyen de tramandar lo fichiér « $1 ».",
        "backend-fail-backup": "Y at pas moyen d’encartar lo fichiér « $1 ».",
        "backend-fail-notexists": "Lo fichiér $1 ègziste pas.",
        "backend-fail-copy": "Y at pas moyen de copiyér lo fichiér « $1 » vers « $2 ».",
        "backend-fail-move": "Y at pas moyen de dèplaciér lo fichiér « $1 » vers « $2 ».",
        "backend-fail-opentemp": "Y at pas moyen d’uvrir lo fichiér temporèro.",
-       "backend-fail-writetemp": "Y at pas moyen d’ècrire dedens lo fichiér temporèro.",
+       "backend-fail-writetemp": "Y at pas moyen d’ècrire sur lo fichiér temporèro.",
        "backend-fail-closetemp": "Y at pas moyen de cllôre lo fichiér temporèro.",
        "backend-fail-read": "Y at pas moyen de liére lo fichiér « $1 ».",
        "backend-fail-create": "Y at pas moyen d’ècrire lo fichiér « $1 ».",
        "backend-fail-maxsize": "Y at pas moyen d’ècrire lo fichiér « $1 » perce qu’il est ples grôs {{PLURAL:$2|qu’un octèt|que $2 octèts}}.",
-       "backend-fail-readonly": "Ora lo sistèmo de stocâjo « $1 » est justo en lèctura. La rêson balyêe est : « ''$2'' »",
+       "backend-fail-readonly": "Ora lo sistèmo de stocâjo « $1 » est mas qu’en lèctura. La rêson balyêe est : <em>$2</em>",
        "backend-fail-synced": "Lo fichiér « $1 » est dens un ètat dèsordonâ dedens los sistèmos de stocâjo de dedens",
        "backend-fail-connect": "Y at pas moyen de sè branchiér u sistèmo de stocâjo « $1 ».",
        "backend-fail-internal": "Na fôta encognua est arrevâye dedens lo sistèmo de stocâjo « $1 ».",
        "backend-fail-contenttype": "Y at pas moyen de dètèrmenar lo tipo de contegnu du fichiér a stocar dedens « $1 ».",
        "backend-fail-batchsize": "Lo sistèmo de stocâjo at balyê na pârt de $1 {{PLURAL:$1|opèracion|opèracions}} de fichiér ; la limita est $2 {{PLURAL:$2|opèracion|opèracions}}.",
-       "backend-fail-usable": "Y at pas moyen de liére d’ècrire lo fichiér « $1 » a côsa de pèrmissions ensufisentes ou ben de rèpèrtouèros / conteniors manquents.",
-       "filejournal-fail-dbconnect": "Y at pas moyen de sè branchiér a la bâsa de balyês du jornal por lo sistèmo de stocâjo « $1 ».",
-       "filejournal-fail-dbquery": "Y at pas moyen de betar a jorn la bâsa de balyês du jornal por lo sistèmo de stocâjo « $1 ».",
-       "lockmanager-notlocked": "Y at pas moyen de dèvèrrolyér « $1 » ; il est pas vèrrolyê.",
+       "backend-fail-usable": "Y at pas moyen de liére d’ècrire lo fichiér « $1 » a côsa de drêts ensufisents ou ben de rèpèrtouèros conteniors manquents.",
+       "filejournal-fail-dbconnect": "Y at pas moyen de sè branchiér a la bâsa de balyês du jornâl por lo sistèmo de stocâjo « $1 ».",
+       "filejournal-fail-dbquery": "Y at pas moyen de betar a jorn la bâsa de balyês du jornâl por lo sistèmo de stocâjo « $1 ».",
+       "lockmanager-notlocked": "Y at pas moyen de dècotar « $1 » ; o est pas cotâ.",
        "lockmanager-fail-closelock": "Y at pas moyen de cllôre lo fichiér de vèrroly por « $1 ».",
        "lockmanager-fail-deletelock": "Y at pas moyen de suprimar lo fichiér de vèrroly por « $1 ».",
        "lockmanager-fail-acquirelock": "Y at pas moyen d’avêr lo vèrroly por « $1 ».",
        "lockmanager-fail-releaselock": "Y at pas moyen de relâchiér lo vèrroly por « $1 ».",
        "lockmanager-fail-db-bucket": "Y at pas moyen de sè veriér vers prod de bâses de balyês de vèrroly dedens la sèlye $1.",
        "lockmanager-fail-db-release": "Y at pas moyen de relâchiér los vèrrolys sur la bâsa de balyês $1.",
-       "lockmanager-fail-svr-acquire": "Y at pas moyen d’avêr des vèrrolys sur lo sèrvior $1.",
+       "lockmanager-fail-svr-acquire": "Y at pas moyen d’avêr de vèrrolys sur lo sèrvior $1.",
        "lockmanager-fail-svr-release": "Y at pas moyen de relâchiér los vèrrolys sur lo sèrvior $1.",
        "zip-file-open-error": "Na fôta est arrevâye pendent l’uvèrtura du fichiér por los contrôlos ZIP.",
        "zip-wrong-format": "Lo fichiér spècifiâ est pas un fichiér ZIP.",
        "zip-bad": "Lo fichiér est un fichiér ZIP corrompu ou ben ôtrament iliésiblo.\nPôt pas étre controlâ coment fôt por la sècuritât.",
-       "zip-unsupported": "Lo fichiér est un fichiér ZIP qu’emplèye des fonccionalitâts ZIP pas recognues per MediaWiki.\nPôt pas étre controlâ coment fôt por la sècuritât.",
+       "zip-unsupported": "Lo fichiér est un fichiér ZIP qu’emplèye de fonccionalitâts ZIP pas recognues per MediaWiki.\nPôt pas étre controlâ coment fôt por la sècuritât.",
        "uploadstash": "Cacho de tèlèchargement",
-       "uploadstash-summary": "Ceta pâge balye accès ux fichiérs que sont tèlèchargiês ou ben en côrs de tèlèchargement, mas sont p’oncor publeyês dedens lo vouiqui. Celos fichiérs sont p’oncor visiblos, solament por l’utilisator que los at tèlèchargiês.",
-       "uploadstash-clear": "Èfaciér los fichiérs en cacho",
+       "uploadstash-summary": "Cela pâge balye accès ux fichiérs que sont tèlèchargiês ou ben en cors de tèlèchargement, mas sont p’oncor publeyês dedens lo vouiqui. Celos fichiérs sont p’oncor visiblos, solament por l’utilisator que los at tèlèchargiês.",
+       "uploadstash-clear": "Vouedar los fichiérs en cacho",
        "uploadstash-nofiles": "Vos éd gins de fichiér en cacho.",
        "uploadstash-badtoken": "L’ègzécucion de cel’accion at pas reussi, pôt-étre perce que voutros identifients de changement ant èxpirâ. Tornâd èprovar.",
        "uploadstash-errclear": "L’èfacement des fichiérs at pas reussi.",
        "uploadstash-refresh": "Rafrèchir la lista des fichiérs",
        "invalid-chunk-offset": "Dèplacement de bocon pas justo",
        "img-auth-accessdenied": "Accès refusâ",
-       "img-auth-nopathinfo": "PATH_INFO manquenta.\nVoutron sèrvior est pas configurâ por passar cel’enformacion.\nPôt étre bâsâye sur CGI et vêr pas recognetre « img_auth ».\nVêde https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+       "img-auth-nopathinfo": "PATH_INFO manquent.\nVoutron sèrvior est pas configurâ por passar cel’enformacion.\nPôt-étre que fonccione en CGI et recognêt vêr pas img_auth.\nVêde https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "Lo chemin demandâ est pas lo rèpèrtouèro de tèlèchargement configurâ.",
        "img-auth-badtitle": "Y at pas moyen de construire un titro justo dês « $1 ».",
        "img-auth-nologinnWL": "Vos éte pas branchiê et pués « $1 » est pas dedens la lista blanche.",
        "img-auth-nofile": "Lo fichiér « $1 » ègziste pas.",
-       "img-auth-isdir": "Vos èprovâd d’arrevar u rèpèrtouèro « $1 ».\nSolament l’accès ux fichiérs est pèrmês.",
+       "img-auth-isdir": "Vos èprovâd d’arrevar u rèpèrtouèro « $1 ».\nSolament l’accès ux fichiérs est pèrmetu.",
        "img-auth-streaming": "Lèctura en continu de « $1 ».",
-       "img-auth-public": "La fonccion de img_auth.php est de sortir des fichiérs d’un vouiqui privâ.\nCeti vouiqui est configurâ coment un vouiqui publico.\nPor na sècuritât pèrfèta, img_auth.php est dèsactivâ.",
-       "img-auth-noread": "L’utilisator at pas accès a la lèctura de « $1 ».",
+       "img-auth-public": "La fonccion d’img_auth.php est de sortir de fichiérs d’un vouiqui privâ.\nCél vouiqui est configurâ coment un vouiqui publico.\nPor na sècuritât parfèta, img_auth.php est dèsactivâ.",
+       "img-auth-noread": "L’utilisator at pas accès en lèctura de « $1 ».",
        "http-invalid-url": "URL pas justa : $1",
        "http-invalid-scheme": "Les URLs avouéc lo plan « $1 » sont pas recognues.",
        "http-request-error": "La demanda HTTP at pas reussi a côsa d’una fôta encognua.",
        "upload-curl-error6": "Y at pas moyen d’avengiér l’URL",
        "upload-curl-error6-text": "L’URL balyêe pôt pas étre avengiêe.\nSe vos plét, tornâd controlar que l’URL est justa et pués que lo seto est en legne.",
        "upload-curl-error28": "Dèlê dèpassâ pendent lo tèlèchargement",
-       "upload-curl-error28-text": "Lo seto at tardâ bien a rèpondre.\nSe vos plét, controlâd que lo seto est en legne, atende un pou et pués tornâd èprovar.\nVos pouede asse-ben èprovar a un’hora de muendra afluence.",
+       "upload-curl-error28-text": "Lo seto at tardâ ben a rèpondre.\nSe vos plét, controlâd que lo seto est en legne, atende-vos un petiôt moment et pués tornâd èprovar.\nVos pouede asse-ben èprovar a un’hora de muendra borrâ.",
        "license": "Licence :",
        "license-header": "Licence",
        "nolicense": "Pas yona chouèsia",
+       "licenses-edit": "Changiér los chouèx de licence",
        "license-nopreview": "(Apèrçu pas disponiblo)",
-       "upload_source_url": " (un’URL justa et accèssibla publicament)",
-       "upload_source_file": " (un fichiér sur voutron ordenator)",
-       "listfiles-summary": "Ceta pâge spèciâla montre tôs los fichiérs tèlèchargiês.\nQuand el est filtrâye per utilisator, solament los fichiérs que la vèrsion la ples novèla est étâye tèlèchargiêe per cél utilisator sont montrâs.",
-       "listfiles_search_for": "Rechèrchiér un nom de fichiér mèdia :",
+       "upload_source_url": "(lo fichiér que vos éd chouèsi dês un’URL justa et accèssibla publicament)",
+       "upload_source_file": "(lo fichiér que vos éd chouèsi dês voutron ordenator)",
+       "listfiles-delete": "suprimar",
+       "listfiles-summary": "Cela pâge spèciâla montre tôs los fichiérs tèlèchargiês.",
+       "listfiles_search_for": "Rechèrchiér un nom de fichiér multimèdiâ :",
+       "listfiles-userdoesnotexist": "Lo comptio utilisator « $1 » est pas encartâ.",
        "imgfile": "fichiér",
        "listfiles": "Lista de fichiérs",
-       "listfiles_thumb": "Figura",
+       "listfiles_thumb": "Miniatura",
        "listfiles_date": "Dâta",
        "listfiles_name": "Nom",
        "listfiles_user": "Utilisator",
        "listfiles_size": "Talye",
        "listfiles_description": "Dèscripcion",
        "listfiles_count": "Vèrsions",
+       "listfiles-show-all": "Entrebetar les vielyes vèrsions de les émâges",
+       "listfiles-latestversion": "Vèrsion d’ora",
+       "listfiles-latestversion-yes": "Ouè",
+       "listfiles-latestversion-no": "Nan",
        "file-anchor-link": "Fichiér",
        "filehist": "Historico du fichiér",
        "filehist-help": "Cllicar dessus na dâta et hora por vêre lo fichiér coment il ére a cél moment.",
        "filehist-datetime": "Dâta et hora",
        "filehist-thumb": "Miniatura",
        "filehist-thumbtext": "Miniatura por la vèrsion du $1",
-       "filehist-nothumb": "Niona figura",
+       "filehist-nothumb": "Niona miniatura",
        "filehist-user": "Utilisator",
        "filehist-dimensions": "Dimensions",
        "filehist-filesize": "Talye du fichiér",
        "filehist-comment": "Comentèro",
        "imagelinks": "Usâjo du fichiér",
-       "linkstoimage": "{{PLURAL:$1|Cela pâge-que emplèye|Celes $1 pâges-que emplèyont}} ceti fichiér :",
-       "linkstoimage-more": "Més {{PLURAL:$1|d’una pâge emplèye|de $1 pâges emplèyont}} ceti fichiér.\nCeta lista montre ren que {{PLURAL:$1|la premiére pâge qu’emplèye|les $1 premiéres pâges qu’emplèyont}} ceti fichiér.\nNa [[Special:WhatLinksHere/$2|lista complèta]] est disponibla.",
-       "nolinkstoimage": "Niona pâge emplèye ceti fichiér.",
-       "morelinkstoimage": "Vêde [[Special:WhatLinksHere/$1|més de lims]] de vers ceti fichiér.",
+       "linkstoimage": "{{PLURAL:$1|Cela pâge-que emplèye|Celes $1 pâges-que emplèyont}} cél fichiér :",
+       "linkstoimage-more": "Més {{PLURAL:$1|d’una pâge emplèye|de $1 pâges emplèyont}} cél fichiér.\nCela lista-que montre mas que {{PLURAL:$1|la premiére pâge qu’emplèye|les $1 premiéres pâges qu’emplèyont}} cél fichiér.\nNa [[Special:WhatLinksHere/$2|lista complèta]] est disponibla.",
+       "nolinkstoimage": "Niona pâge emplèye cél fichiér.",
+       "morelinkstoimage": "Vêde [[Special:WhatLinksHere/$1|més de lims]] de vers cél fichiér.",
        "linkstoimage-redirect": "$1 (redirèccion de fichiér) $2",
        "duplicatesoffile": "{{PLURAL:$1|Cél fichiér-que est un doblo|Celos $1 fichiérs-que sont des doblos}} de ceti ([[Special:FileDuplicateSearch/$2|més de dètalys]]) :",
-       "sharedupload": "Ceti fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.",
-       "sharedupload-desc-there": "Ceti fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.\nSe vos plét, vêde la sina [$2 pâge de dèscripcion] por més d’enformacions.",
+       "sharedupload": "Cél fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.",
+       "sharedupload-desc-there": "Cél fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.\nSe vos plét, vêde sa [$2 pâge de dèscripcion] por més d’enformacions.",
        "sharedupload-desc-here": "Cél fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.\nLa dèscripcion dessus sa [$2 pâge de dèscripcion] est montrâye ce-desot.",
-       "sharedupload-desc-edit": "Ceti fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.\nPôt-étre vos voléd changiér la dèscripcion sur la sina [$2 pâge de dèscripcion].",
-       "sharedupload-desc-create": "Ceti fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.\nPôt-étre vos voléd changiér la dèscripcion sur la sina [$2 pâge de dèscripcion].",
+       "sharedupload-desc-edit": "Cél fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.\nPôt-étre vos voléd changiér la dèscripcion dessus sa [$2 pâge de dèscripcion].",
+       "sharedupload-desc-create": "Cél fichiér vint de $1 et pôt étre empleyê per d’ôtros projèts.\nPôt-étre vos voléd changiér la dèscripcion dessus sa [$2 pâge de dèscripcion].",
        "filepage-nofile": "Nion fichiér de cél nom ègziste.",
        "filepage-nofile-link": "Nion fichiér de cél nom ègziste, mas vos en pouede [$1 tèlèchargiér yon].",
-       "uploadnewversion-linktext": "Tèlèchargiér na novèla vèrsion de ceti fichiér",
+       "uploadnewversion-linktext": "Tèlèchargiér na novèla vèrsion de cél fichiér",
        "shared-repo-from": "de : $1",
        "shared-repo": "un dèpôt partagiê",
        "filepage.css": "/* Lo code CSS betâ ique est encllu dens la pâge de dèscripcion du fichiér, et pués dens los vouiquis cliants ètrangiérs. */",
-       "upload-disallowed-here": "Vos pouede pas ècllafar ceti fichiér.",
+       "upload-disallowed-here": "Vos pouede pas ècllafar cél fichiér.",
        "filerevert": "Rèvocar $1",
        "filerevert-legend": "Rèvocar lo fichiér",
-       "filerevert-intro": "Vos éte prèst a rèvocar lo fichiér '''[[Media:$1|$1]]''' a la [$4 vèrsion du $2 a $3].",
+       "filerevert-intro": "Vos éte prèst a rèvocar lo fichiér <strong>[[Media:$1|$1]]</strong> a la [$4 vèrsion du $2 a $3].",
        "filerevert-comment": "Rêson :",
        "filerevert-defaultcomment": "Rèvocâ a la vèrsion du $1 a $2 ($3)",
        "filerevert-submit": "Rèvocar",
-       "filerevert-success": "'''[[Media:$1|$1]]''' est étâ rèvocâ a la [$4 vèrsion du $2 a $3].",
+       "filerevert-success": "<strong>[[Media:$1|$1]]</strong> est étâ rèvocâ a la [$4 vèrsion du $2 a $3].",
        "filerevert-badversion": "Y at gins de vèrsion locâla devant de cél fichiér avouéc l’horodatâjo balyê.",
        "filedelete": "Suprimar $1",
        "filedelete-legend": "Suprimar lo fichiér",
-       "filedelete-intro": "Vos éte prèst a suprimar lo fichiér '''[[Media:$1|$1]]''' et pués tot lo sin historico.",
-       "filedelete-intro-old": "Vos éte aprés suprimar la vèrsion de '''[[Media:$1|$1]]''' du [$4 $2 a $3].",
+       "filedelete-intro": "Vos éte prèst a suprimar lo fichiér <strong>[[Media:$1|$1]]</strong> et pués tot son historico.",
+       "filedelete-intro-old": "Vos éte aprés suprimar la vèrsion de <strong>[[Media:$1|$1]]</strong> du [$4 $2 a $3].",
        "filedelete-comment": "Rêson :",
        "filedelete-submit": "Suprimar",
-       "filedelete-success": "'''$1''' est étâ suprimâ.",
-       "filedelete-success-old": "La vèrsion de '''[[Media:$1|$1]]''' du $2 a $3 est étâye suprimâye.",
-       "filedelete-nofile": "'''$1''' ègziste pas.",
-       "filedelete-nofile-old": "Ègziste gins de vèrsion arch·ivâye de '''$1''' avouéc los atributs spècifiâs.",
-       "filedelete-otherreason": "Ôtra rêson / rêson de ples :",
+       "filedelete-success": "<strong>$1</strong> est étâ suprimâ.",
+       "filedelete-success-old": "La vèrsion de <strong>[[Media:$1|$1]]</strong> du $2 a $3 est étâye suprimâye.",
+       "filedelete-nofile": "<strong>$1</strong> ègziste pas.",
+       "filedelete-nofile-old": "Ègziste gins de vèrsion arch·ivâye de <strong>$1</strong> avouéc los atributs spècifiâs.",
+       "filedelete-otherreason": "Ôtra rêson ou ben rêson de més :",
        "filedelete-reason-otherlist": "Ôtra rêson",
-       "filedelete-reason-dropdown": "*Rêsons corentes de suprèssion\n** Violacion du drêt d’ôtor\n** Fichiér en doblo",
+       "filedelete-reason-dropdown": "*Rêsons comenes de suprèssion\n** Violacion du drêt d’ôtor\n** Fichiér en doblo",
        "filedelete-edit-reasonlist": "Changiér les rêsons de suprèssion",
        "filedelete-maintenance": "La suprèssion et la rèstoracion de fichiérs est dèsactivâye por un moment pendent la mantegnence.",
        "filedelete-maintenance-title": "Y at pas moyen de suprimar lo fichiér",
index eb82d6f..21ec0d4 100644 (file)
@@ -11,7 +11,8 @@
                        "Supriya kankumbikar",
                        "Vaishali Parab",
                        "The Discoverer",
-                       "Cliffa fernandes"
+                       "Cliffa fernandes",
+                       "Rxy"
                ]
        },
        "tog-hideminor": "हालींच बदल केल्ल्यांतले बारीक संपादन लिपय",
@@ -24,7 +25,7 @@
        "tog-enotifwatchlistpages": "म्हज्या सादुरवळेरेंतलें पान वा फायल बदल्ली जाल्यार म्हाका इमेल करात",
        "tog-shownumberswatching": "ध्यान दवरपी वांगड्यांची संख्या दाखय",
        "tog-oldsig": "सद्याची निशाणी",
-       "tog-uselivepreview": "लायव à¤ªà¥\81रà¥\8dवनियाळाà¤\9aà¥\8b à¤\89पà¥\87à¤\97 à¤\95र",
+       "tog-uselivepreview": "लायव à¤ªà¥\81रà¥\8dवनियाळाà¤\9aà¥\8b à¤µà¤¾à¤ªर",
        "tog-watchlisthideown": "सादुरवळेरीतलें म्हजे संपादन लिपय",
        "tog-watchlisthidebots": "ध्यानसुचीतले रोबोट संपादन लिपय",
        "tog-watchlisthideminor": "सादुरवळेरीतले ल्हान संपादन लिपय",
        "and": "&#32;आनीक",
        "qbfind": "सोदात",
        "qbbrowse": "ब्राउज",
-       "qbedit": "सà¤\82पादन",
+       "qbedit": "बदल",
        "qbpageoptions": "हें पान",
        "qbmyoptions": "म्हजी पानां",
        "faq": "परत परत विचारिल्ले प्रस्न",
        "print": "छाप",
        "view": "पळय",
        "view-foreign": " $1 चेर पळयात",
-       "edit": "सà¤\82पादन",
+       "edit": "बदल",
        "edit-local": "थळाव्या संपादनाचें वर्णन",
        "create": "निर्माण कर",
        "create-local": "थळावे वर्णन जोडात",
        "newpage": "नवें पान",
        "talkpage": "ह्या पानाचेर चर्चा करात",
        "talkpagelinktext": "चर्चा",
-       "specialpage": "à¤\96ाशà¥\87लà¥\87à¤\82 पान",
+       "specialpage": "विशà¥\87श पान",
        "personaltools": "खाजगी साधनां",
        "articlepage": "मजकूर पान पळयात",
        "talk": "भासाभास",
        "redirectedfrom": "($1 सून पुनर्निर्देशित)",
        "redirectpagesub": "पान परतून निर्देशीत करचें",
        "redirectto": "हांगां पुनर्निर्देशित:",
-       "lastmodifiedat": " ह्या पानांत निमाणो बदल,$1 वेर $2 वेळार केल्लो",
+       "lastmodifiedat": "ह्या पानांत निमाणो बदल,$1 वेर $2 वेळार केल्लो",
        "protectedpage": "राखून दवरिल्लें पान",
        "jumpto": "हुपून वचात:",
        "jumptonavigation": "दिशा-नियंत्रण",
        "ok": "बरें",
        "retrievedfrom": "\"$1\" चे कडल्यान परतून मेळयलें",
        "youhavenewmessagesmulti": "$1 चेर तुका नवो संदेश आसा",
-       "editsection": "सà¤\82पादन",
-       "editold": "सà¤\82पादन",
+       "editsection": "बदल",
+       "editold": "बदल",
        "viewsourceold": "उगम पळेयात",
        "editlink": "बदल",
        "viewsourcelink": "उगम पळयात",
        "nstab-main": "पान",
        "nstab-user": "वापरपी पान",
        "nstab-media": "माध्यमाचें पान",
-       "nstab-special": "à¤\96ाशà¥\87लà¥\87à¤\82 पान",
+       "nstab-special": "विशà¥\87श पान",
        "nstab-project": "प्रकल्पाचें पान",
        "nstab-image": "फायल",
        "nstab-mediawiki": "संदेश",
        "nstab-help": "आदाराचें पान",
        "nstab-category": "वर्ग",
        "nosuchaction": "असले तरेचे कार्य ना",
-       "nosuchspecialpage": "à¤\85सलà¥\87 à¤\95ाà¤\82यà¤\9a à¤\96ाशà¥\87लà¥\87à¤\82 पान ना",
+       "nosuchspecialpage": "à¤\85सलà¥\87 à¤\95ाà¤\82यà¤\9a à¤µà¤¿à¤¶à¥\87श पान ना",
        "error": "चूक",
        "databaseerror": "डॅटाबॅज त्रुटी",
        "databaseerror-textcl": "डॅटाबेज विरोध त्रुटी आयिल्ली आसा",
        "image_tip": "अंत: स्थापीत फायल",
        "media_tip": "फायलीची जोडणी",
        "sig_tip": "वेळ-छाप सयत तुमची निशाणी",
-       "hr_tip": "à¤\86डवà¥\80 à¤µà¤³ (à¤\89णà¥\8b à¤\89पà¥\87à¤\97 à¤\95रचो)",
+       "hr_tip": "à¤\86डवà¥\80 à¤µà¤³ (à¤\89णà¥\8b à¤µà¤¾à¤ªरचो)",
        "summary": "आपरोस:",
        "subject": "विशय/माथाळो",
        "minoredit": "हें दाकटें संपादन",
        "prefs-watchlist": "सादुरवळेरी",
        "youremail": "इमेल",
        "yourrealname": "खरें नांवः",
-       "right-writeapi": "बरà¥\8bवपाà¤\9aà¥\87 API à¤\9aà¥\8b à¤\89पà¥\87à¤\97 à¤\95रात",
+       "right-writeapi": "बरà¥\8bवपाà¤\9aà¥\87 API à¤µà¤¾à¤ªरात",
        "newuserlogpage": "वापरपी रोचनेचे वळेरी",
        "action-edit": "हें पान संपादीत कर",
        "nchanges": "$1 {{PLURAL:$1|बदल|बदल}}",
        "statistics": "संख्याशास्त्र",
        "statistics-pages": "पान:",
        "statistics-files": "फायल अपलोड करात",
-       "brokenredirects-edit": "सà¤\82पादन",
+       "brokenredirects-edit": "बदल",
        "brokenredirects-delete": "काडून उडयात",
        "nbytes": "$1 {{PLURAL:$1|बाय्ट}}",
        "nmembers": "$1 {{PLURAL:$1|वांगडी}}",
        "changecontentmodel-reason-label": "कारण:",
        "protectlogpage": "सुरक्षितेचें सोत्र",
        "protectedarticle": "राखिल्ले\"[[$1]]\"",
-       "restriction-edit": "सà¤\82पादन",
+       "restriction-edit": "बदल",
        "restriction-move": "दुसरेकडे व्हरात",
        "restriction-create": "निर्माण कर",
        "undeletelink": "पळेयात/परत हाडात",
        "tooltip-t-contributions": "ह्या वापरप्याची योगदानाची वळेरी",
        "tooltip-t-emailuser": "ह्या उपेगकर्त्याक इ-मेल धाडात",
        "tooltip-t-upload": "फायली अपलोड करात",
-       "tooltip-t-specialpages": "सà¤\97ळà¥\8dया à¤\96ाशà¥\87लà¥\8dया पानांची वळेरी",
+       "tooltip-t-specialpages": "सà¤\97ळà¥\8dया à¤µà¤¿à¤¶à¥\87श पानांची वळेरी",
        "tooltip-t-print": "ह्या पानाची छापपायोग्य आवृत्ती",
        "tooltip-t-permalink": "ह्या पानाच्या ह्या पुनर्नियाळाकडे सदांकाळ जोडणी",
        "tooltip-ca-nstab-main": "मजकूर पान पळेयात",
        "watchlisttools-view": "प्रस्तूत बदल पळयात.",
        "watchlisttools-edit": "सादुरवळेरी पळय आनी संपादीत करात",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|उलयात]])",
-       "specialpages": "à¤\96ाशà¥\87लà¥\80à¤\82 पानां",
+       "specialpages": "विशà¥\87श पानां",
        "tag-filter": "[[Special:Tags|कुर्वेचीट]] गाळणो:",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|कुरवेचीट|कुरवेचीटी}}]]: $2)",
        "htmlform-title-not-exists": "$1 अस्तित्वांत ना.",
index 28125ae..e135d4b 100644 (file)
        "newpage": "Novem pan",
        "talkpage": "Hea panachem bhasabhas kor",
        "talkpagelinktext": "Bhasabhas",
-       "specialpage": "Khaxhel pan",
+       "specialpage": "Vixex pan",
        "personaltools": "Khasgi avtam",
        "articlepage": "Vixoi sombondhi pan poloi",
        "talk": "Bhasabhas",
        "nstab-main": "Pan",
        "nstab-user": "Vapuddpeachem pan",
        "nstab-media": "Madheomachem pan",
-       "nstab-special": "Khaxelem pan",
+       "nstab-special": "Vixex pan",
        "nstab-project": "Prokolpache pan",
        "nstab-image": "Fail",
        "nstab-mediawiki": "Sondex",
        "nstab-category": "Vorg",
        "mainpage-nstab": "Mukhel pan",
        "nosuchaction": "Oslem torechem karya nam",
-       "nosuchspecialpage": "Oslem kaich khaxellem pan na",
+       "nosuchspecialpage": "Oslem kaich vixex pan na",
        "error": "Chuk",
        "databaseerror": "Totv-kox chuk",
        "databaseerror-textcl": "Totv-kox (database) sodtana chuk ghodli",
        "template-semiprotected": "(ordhem rakhun dovorlelem)",
        "hiddencategories": "Hem pan {{PLURAL:$1|1 lipoilelea vorgacho vangddi|$1 lipoileleam vorgancho vangddi}}:",
        "permissionserrorstext-withaction": "$2, hem korpak tuka porvangi na, {{PLURAL:$1|hea karnnak lagon|hea karnnank lagun}}:",
-       "recreate-moveddeleted-warn": "<strong>Xittkavnni: Tum ek pan porot rochtai jem fattim kadun udoilelem.<strong>\n\nPanacho sudar korop sarkem zalear dhean di.\nPan vogllavpachem ani sotr halovpachem, sovloti khatir hangasor dilelem asa:",
+       "recreate-moveddeleted-warn": "<strong>Xittkavnni: Tum ek pan porot rochtai jem fattim kadun udoilelem.<strong>\n\nPanacho sudar korop sarkem zalear dhean di.\nPan kadoupachem ani halovpachem sotr, sovloti khatir hangasor dilelem asa:",
        "moveddeleted-notice": "Hem pan kadun udoilelem asa.\nPanachea kadun udounechi ani hallovnechi sotr sondorba khatir sokoil dilea.",
        "content-model-wikitext": "wikimozkur",
        "content-model-text": "Sado mozkur",
        "prefs-help-email": "Email potto sokticho na, pun tum gupitutor visroxi zalear gupitutor punorsthapon korunk email pottechi goroz podta.",
        "prefs-help-email-others": "Tujean dusreank tujea vapurpeacho panar vo bhasabhasache panar aslele eke email zodde vorvim tuje xim sompork korunk diunk zata.\nDusre tuje xim sompork kortat tednam tuzo email potto tankam kollchenam.",
        "group-all": "(soglle)",
-       "right-writeapi": "Borovpeache API-cho upeog",
+       "right-writeapi": "Borovpeache API-cho vapor",
        "newuserlogpage": "Vapurpi rochnnechem sotr",
        "action-edit": "hem pan sudar",
        "nchanges": "$1 {{PLURAL:$1|bodlop|bodlopam}}",
        "delete-legend": "Kadun udoi",
        "actioncomplete": "Karvai sompurnn",
        "actionfailed": "Karvai oiesiesvi",
-       "dellogpage": "Vogllaoneche sotr",
+       "dellogpage": "Kadun udouneche sotr",
        "deleteotherreason": "Dusrem/aniki karon:",
        "rollbacklink": "kovoll",
        "rollbacklinkcount": "$1 {{PLURAL:$1|bodol|bodlopam}} kovoll",
        "tooltip-t-contributions": "{{GENDER:$1|Hea vapuddpeachea}} yogdanachi suchi",
        "tooltip-t-emailuser": "{{GENDER:$1|Hea vapuddpeak}} email patthoi",
        "tooltip-t-upload": "Faili upload kor",
-       "tooltip-t-specialpages": "Sogllea khaxelim pananchi volleri",
+       "tooltip-t-specialpages": "Sogllea vixex pananchi volleri",
        "tooltip-t-print": "Hea panachem chap'pakyogya avrutti",
        "tooltip-t-permalink": "Hea panache hea uzollnnek togpi zoddni",
        "tooltip-ca-nstab-main": "Mozkur pan polloi",
        "watchlisttools-raw": "Sadurvollerichi mull-an bodol kor",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|uloi]])",
        "duplicate-defaultsort": "'''Chotrai:''' Falta anukraman mukhel ''$2'' rodd korta adhlem falta anukraman mukhel ''$1'', haka.",
-       "specialpages": "Khaxelim Panam",
+       "specialpages": "Vixex panam",
        "external_image_whitelist": " #Hi voll asa toxich dovor<pre>\n#Khala sodpache sache (''regular expressions'') ghal (fokot // modem voita poi tem bhag)\n#Hanche borobor bhaile zodlele murt comparar kel'le zatele\n#Mell khatat tim murt koxeo distele, na zalear fokot mortek ek zodd distele\n#Jeo voll #-an suru zatele tem vivek mhunn manlele zatele\n#Hanga vhodle and dhakte okxora modem forok podona\n\n#Soglle sodpache sache hea volla voir ghal. Hi voll asa toxich dovor</pre>",
        "tag-filter": "[[Special:Tags|Kurvechit]] challni:",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Kurvechit|Kurvechiti}}]]: $2)",
index 136f62f..990669d 100644 (file)
        "changecontentmodel-nodirectediting": "Il modello di contenuto $1 non supporta la modifica diretta",
        "log-name-contentmodel": "Modifiche del modello contenuti",
        "log-description-contentmodel": "Eventi relativi al modello di contenuto di una pagina",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|ha creato}} la pagina $3 utilizzando un modello di contenuto non predefinito \"$5\"",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|ha modificato}} il modello di contenuto della pagina $3 da \"$4\" a \"$5\"",
        "logentry-contentmodel-change-revertlink": "ripristina",
        "logentry-contentmodel-change-revert": "ripristina",
index 3e6c7c1..4b89335 100644 (file)
        "blocked-mailpassword": "Jūsų IP adresas yra užblokuotas nuo redagavimo, taigi neleidžiama naudoti slaptažodžio priminimo funkcijos, kad apsisaugotume nuo piktnaudžiavimo.",
        "eauthentsent": "Patvirtinimo laiškas buvo nusiųstas į paskirtąjį el. pašto adresą.\nPrieš išsiunčiant kitą laišką į jūsų dėžutę, jūs turite vykdyti nurodymus laiške, kad patvirtintumėte, kad dėžutė tikrai yra jūsų.",
        "throttled-mailpassword": "Slaptažodžio priminimas jau buvo išsiųstas, per {{PLURAL:$1|$1 paskutinę valandą|$1 paskutines valandas|$1 paskutinių valandų}}.\n\nNorint apsisaugoti nuo piktnaudžiavimo, slaptažodžio priminimas gali būti išsiųstas tik kas {{PLURAL:$1|$1 valandą|$1 valandas|$1 valandų}}.",
-       "mailerror": "Klaida siunčiant paštą: $1",
+       "mailerror": "Klaida siunčiant laišką: $1",
        "acct_creation_throttle_hit": "Šio projekto lankytojai, naudojantys jūsų IP adresą, sukūrė {{PLURAL:$1|$1 paskyrą|$1 paskyras|$1 paskyrų}} per paskutiniąją dieną, o tai yra didžiausias leidžiamas kiekis per šį laiko tarpą.\nTodėl šiuo metu lankytojai, naudojantys šį IP adresą, daugiau negali kurti paskyrų.",
        "emailauthenticated": "Jūsų el. pašto adresas buvo patvirtintas $2 d. $3.",
        "emailnotauthenticated": "Jūsų el. pašto adresas dar nėra patvirtintas. Jokie laiškai\nnebus siunčiami nei vienai žemiau išvardintai paslaugai.",
        "mergehistory-empty": "Versijos negali būti sujungtos",
        "mergehistory-done": "$3 $1 {{PLURAL:$3|versija|versijos|versijų}} sėkmingai {{PLURAL:$3|sujungta|sujungtos|sujungta}} su [[:$2]].",
        "mergehistory-fail": "Nepavyksta atlikti istorijų sujungimo, prašome patikrinti puslapio ir laiko parametrus.",
+       "mergehistory-fail-invalid-dest": "Paskirties puslapis yra neteisingas.",
        "mergehistory-fail-toobig": "Nepavyksta sulieti istorijos, nes būtina pernešti daugiau, nei leidžia $1 riba, {{PLURAL:$1|versijos|versijų}}.",
        "mergehistory-no-source": "Šaltinio puslapis $1 neegzistuoja.",
        "mergehistory-no-destination": "Rezultato puslapis $1 neegzistuoja.",
        "apisandbox": "API smėlio dėžės",
        "apisandbox-api-disabled": "API yra išjungtas šioje svetainėje.",
        "apisandbox-intro": "Naudokite šį puslapį norėdami eksperimentuoti su '''MediaWiki API \"„.\n\tIeškokite [//www.mediawiki.org/wiki/API:Main_page API dokumentacijoje] Išsamesnės informacijos apie API naudojimo.",
+       "apisandbox-fullscreen": "Išplėsti skydelį",
+       "apisandbox-unfullscreen": "Rodyti puslapį",
        "apisandbox-submit": "Pateikti prašymą",
        "apisandbox-reset": "Išvalyti",
        "apisandbox-retry": "Bandykite dar kartą",
        "apisandbox-no-parameters": "Šis API modulis neturi parametrų.",
+       "apisandbox-helpurls": "Pagalbinės nuorodos",
        "apisandbox-examples": "Pavyzdžiai",
        "apisandbox-dynamic-parameters": "Papildomi parametrai",
        "apisandbox-dynamic-parameters-add-label": "Pridėti parametrą:",
        "apisandbox-dynamic-parameters-add-placeholder": "Parametro pavadinimas",
+       "apisandbox-dynamic-error-exists": "Parametras, pavadinimu „$1“ jau yra.",
        "apisandbox-deprecated-parameters": "Nebenaudojami parametrai",
        "apisandbox-submit-invalid-fields-title": "Kai kurie laukai yra neteisingi",
        "apisandbox-results": "Rezultatai",
        "apisandbox-request-url-label": "Prašyti URL:",
        "apisandbox-request-time": "Užklausos laikas: $1",
+       "apisandbox-alert-field": "Šio lauko reikšmė yra neteisinga.",
        "booksources": "Knygų šaltiniai",
        "booksources-search-legend": "Knygų šaltinių paieška",
        "booksources-search": "Ieškoti",
        "expand_templates_preview_fail_html": "<em>Nes {{SITENAME}} turi įgalinta gryną HTML ir įvyko sesijos duomenų praradimas, peržiūra yra paslėpta kaip atsargos priemonė prieš JavaScript atakas.</em>\n\n<strong>Jei tai teisėtas peržiūros bandymas, prašome bandyti dar kartą.</strong>\nJei tai vistiek neveikia, pabandykite [[Special:UserLogout|atsijungti]] ir vėl prisijungti.",
        "expand_templates_preview_fail_html_anon": "<em>Nes {{SITENAME}} turi įgalinta gryną HTML ir jūs esate neprisijungęs, peržiūra paslėpta kaip atsargumo priemonė prieš JavaScript atakas.</em>\n\n<strong>Jei tai teisėtas peržiūros bandymas prašome [[Special:UserLogin|prisijungti]] ir bandyti vėl.</strong>",
        "expand_templates_input_missing": "Turite pateikti bent truputį įvesties teksto.",
-       "pagelanguage": "Puslapio kalbos pasirinkimas",
+       "pagelanguage": "Keisti puslapio kalbą",
        "pagelang-name": "Puslapis",
        "pagelang-language": "Kalba",
        "pagelang-use-default": "Naudoti numatytąją kalbą",
index 4d3fe0b..7437611 100644 (file)
        "userrights": "Dalībnieku tiesību pārvaldība",
        "userrights-lookup-user": "Pārvaldīt dalībnieka grupas",
        "userrights-user-editname": "Ievadi lietotājvārdu:",
-       "editusergroup": "Izmainīt dalībnieka grupas",
+       "editusergroup": "Izmainīt {{GENDER:$1|dalībnieka|dalībnieces}} grupas",
        "editinguser": "Izmainīt lietotāja '''[[User:$1|$1]]''' ([[User talk:$1|{{int:talkpagelinktext}}]]{{int:pipe-separator}}[[Special:Contributions/$1|{{int:contribslink}}]]) statusu",
        "userrights-editusergroup": "Izmainīt lietotāja grupas",
-       "saveusergroups": "Saglabāt dalībnieka grupas",
+       "saveusergroups": "Saglabāt {{GENDER:$1|dalībnieka|dalībnieces}} grupas",
        "userrights-groupsmember": "Šobrīd ietilpst grupās:",
        "userrights-groupsmember-auto": "Netiešs dalībnieks:",
        "userrights-groups-help": "Tu vari izmainīt kādās grupās šis lietotājs ir:\n* Ieķeksēts lauciņš norāda, ka lietotājs ir attiecīgajā grupā.\n* Neieķeksēts lauciņš norāda, ka lietotājs nav attiecīgajā grupā.\n* * norāda, ka šo grupu tu nevarēsi noņemt, pēc tam, kad to būsi pielicis, vai otrādāk (tu nevarēsi atcelt savas izmaiņas).",
        "recentchanges-label-bot": "Šis ir bota veikts labojums",
        "recentchanges-label-unpatrolled": "Šis labojums vēl nav pārbaudīts",
        "recentchanges-label-plusminus": "Par tik baitiem tika izmainīts lapas izmērs",
-       "recentchanges-legend-heading": "'''Apzīmējumi:'''",
+       "recentchanges-legend-heading": "<strong>Apzīmējumi:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (skatīt arī [[Special:NewPages|jaunās lapas]])",
        "recentchanges-submit": "Rādīt",
        "rcnotefrom": "Šobrīd redzamas izmaiņas kopš '''$2''' (parādītas ne vairāk par '''$1''').",
        "apisandbox": "API smilškaste",
        "apisandbox-api-disabled": "API ir atspējots šajā tīmekļa vietnē.",
        "apisandbox-reset": "Notīrīt",
-       "apisandbox-examples": "Piemērs",
-       "apisandbox-results": "Rezultāts",
+       "apisandbox-examples": "Piemēri",
+       "apisandbox-results": "Rezultāti",
        "apisandbox-request-url-label": "Pieprasījuma URL:",
        "apisandbox-request-time": "Pieprasījuma izpildes laiks: $1",
        "booksources": "Grāmatu avoti",
        "movenotallowedfile": "Tev nav atļaujas pārvietot failus.",
        "cant-move-user-page": "Tev nav atļaujas pārvietot lietotāju lapas (neskaitot apakšlapas).",
        "cant-move-to-user-page": "Tev nav atļaujas pārvietot lapu uz lietotāja lapu (neskaitot lietotāja lapas apakšlapu).",
-       "newtitle": "Uz šādu lapu",
+       "newtitle": "Jaunais nosaukums:",
        "move-watch": "Uzraudzīt šo lapu",
        "movepagebtn": "Pārvietot lapu",
        "pagemovedsub": "Pārvietošana notikusi veiksmīgi",
        "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|versijas|versija|versijas}} no $2",
        "javascripttest": "JavaScript testēšana",
        "javascripttest-pagetext-unknownaction": "Nezināma darbība \"$1\".",
-       "tooltip-pt-userpage": "Tava lietotāja lapa",
+       "tooltip-pt-userpage": "Tava {{GENDER:|dalībnieka|dalībnieces}} lapa",
        "tooltip-pt-anonuserpage": "Manas IP adreses lietotāja lapa",
-       "tooltip-pt-mytalk": "Tava diskusiju lapa",
+       "tooltip-pt-mytalk": "{{GENDER:|Tava}} diskusiju lapa",
        "tooltip-pt-anontalk": "Diskusija par labojumiem, kas izdarīti no šīs IP adreses",
-       "tooltip-pt-preferences": "Tavas izvēles",
+       "tooltip-pt-preferences": "{{GENDER:|Tavas}} izvēles",
        "tooltip-pt-watchlist": "Tevis uzraudzītās lapas",
        "tooltip-pt-mycontris": "{{GENDER:|Tavs}} devums",
        "tooltip-pt-anoncontribs": "Labojumi, kas veikti no šīs IP adreses",
index 16cc94f..affe36a 100644 (file)
@@ -15,6 +15,7 @@
        "tog-hideminor": "Hanitsika ny fiovana madinika ao amin'ny fiovana farany",
        "tog-hidepatrolled": "Hanitrika ny fiovana voaara-maso ao amin'ny fiovana farany",
        "tog-newpageshidepatrolled": "Hanitsika ny pejy voaara-maso ao amin'ny pejy vaovao",
+       "tog-hidecategorization": "Hanafina ny fansokajiana pejy",
        "tog-extendwatchlist": "Hanitatra ny lisitra fanaraham-pejy mba haneho ny fanovana rehetra fa tsy ny vaovao indrindra fotsiny",
        "tog-usenewrc": "Hampivondrona ny fiovana araky ny pejy ao amin'ny fiovana farany ary ao amin'ny lisitry ny pejy arahana",
        "tog-numberheadings": "Asio laharany ny lohateny",
        "tog-watchlisthidebots": "Asitriho amin'ny lisitro ny fanovàna nataon'ny rôbô",
        "tog-watchlisthideminor": "Tsy aseho ny fisoloina kely anatin'ny pejy fanaraha-maso",
        "tog-watchlisthideliu": "Asitriho amin'ny lisitro ny fanovàna nataon'ny mpikambana hafa",
+       "tog-watchlistreloadautomatically": "Hanavao ny lisitry ny pejy arahana rehefa misy sivana niova (mila JavaScript)",
        "tog-watchlisthideanons": "Asitriho amin'ny lisitro ny fanovana nataon'ny IP",
        "tog-watchlisthidepatrolled": "Asitriho amin'ny lisitro ny fanovàna efa nojerena",
+       "tog-watchlisthidecategorization": "Hanafina ny fanasokajiana pejy",
        "tog-ccmeonemails": "Andefaso tahaka ny imailaka alefako amin'ny mpikambana hafa",
        "tog-diffonly": "Aza ampiseho ny voatonin'ny pejy eo amban'ny diff",
        "tog-showhiddencats": "Asehoy ny sokajy misitrika",
        "databaseerror-query": "Hataka: $1",
        "databaseerror-function": "Lefa: $1",
        "databaseerror-error": "Hadisoana: $1",
+       "transaction-duration-limit-exceeded": "Mba tsy hisian'ny fihalavan'ny fotoam-pandikana be loatra, dia nofoanana ity asa ity satria nihoatra ny fetra $2 {{PLURAL:$2}}segondra ny fotoana nalain'ny asa fanoratana ($1 s). Raha manova zavatra mibolongana be indray mandeha ianao dia zarazarao ho kelikely ny zavatra ataonao.",
        "laggedslavemode": "Fampitandremana: Mety ho tsy nisy fanovana vao haingana angamba io pejy io",
        "readonly": "Mihidy ny banky angona",
        "enterlockreason": "Manomeza antony hanidiana ny pejy, ahitana ny fotoana tokony hamahana izay fihidiana izay",
        "botpasswords": "Tenimiafin-drôbô",
        "botpasswords-disabled": "Tsy ampiasaina ny tenimiafin-drôbô.",
        "botpasswords-existing": "Tenimiafin-drôbô efa misy",
+       "botpasswords-label-appid": "Anarana rôbô:",
+       "botpasswords-label-create": "Foronina",
+       "botpasswords-label-update": "Vaozina",
+       "botpasswords-label-cancel": "Avela",
+       "botpasswords-label-delete": "Fafàna",
+       "botpasswords-label-resetpassword": "Hamerina ny tenimiafina",
+       "botpasswords-label-grants": "Zo mihatra:",
+       "botpasswords-label-grants-column": "Nomena",
+       "botpasswords-bad-appid": "Tsy azo raisina ny anarana rôbô \"$1\".",
+       "botpasswords-insert-failed": "Tsy afaka nanampy ny anarana rôbô \"$1\". Tsy efa nampiana ve ilay izy?",
        "resetpass_forbidden": "Tsy afaka ovaina ny tenimiafina",
        "resetpass-no-info": "Tsy maintsy tafiditra ao amin'ny kaontinao ianao vao afaka mijery ity pejy ity.",
        "resetpass-submit-loggedin": "Ovay ny tenimiafina",
index d91ed95..8fb4001 100644 (file)
        "listgrouprights-namespaceprotection-header": "Ograniczenia przestrzeni nazw",
        "listgrouprights-namespaceprotection-namespace": "Przestrzeń nazw",
        "listgrouprights-namespaceprotection-restrictedto": "Uprawnienia pozwalające użytkownikom na edytowanie",
+       "listgrants": "Uprawnienia",
+       "listgrants-grant": "Uprawnienie",
        "listgrants-rights": "Uprawnienie",
        "trackingcategories": "Śledzenie kategorii",
        "trackingcategories-summary": "Ta strona zawiera listę kategorii monitorujących wypełnianych automatycznie przez oprogramowanie MediaWiki. Nazwy kategorii można zmienić edytując odpowiednie komunikaty systemowe znajdujące się w przestrzeni nazw {{ns:8}}.",
        "delete-toobig": "Ta strona ma bardzo długą historię edycji – ponad $1 {{PLURAL:$1|zmianę|zmiany|zmian}}.<br />\nUsuwanie jej zostało ograniczone ze względu na możliwość zakłócenia pracy {{GRAMMAR:D.lp|{{SITENAME}}}}.",
        "delete-warning-toobig": "Ta strona ma bardzo długą historię edycji – ponad $1 {{PLURAL:$1|zmianę|zmiany|zmian}}.<br />\nBądź ostrożny, ponieważ usunięcie jej może spowodować zakłócenia w pracy {{GRAMMAR:D.lp|{{SITENAME}}}}.",
        "deleteprotected": "Nie możesz usunąć tej strony, ponieważ została zabezpieczona.",
-       "deleting-backlinks-warning": "''' Uwaga:''' Do strony, którą masz zamiar usunąć, odwołują się [[Special:WhatLinksHere/{{FULLPAGENAME}}|inne strony]].",
+       "deleting-backlinks-warning": "<strong>Uwaga:</strong> Do strony, którą masz zamiar usunąć, odwołują się [[Special:WhatLinksHere/{{FULLPAGENAME}}|inne strony]].",
        "rollback": "Cofnij edycję",
        "rollbacklink": "cofnij",
        "rollbacklinkcount": "cofnij $1 {{PLURAL:$1|edycję|edycje|edycji}}",
        "expand_templates_generate_xml": "Pokaż drzewo analizatora składni w formacie XML",
        "expand_templates_generate_rawhtml": "Pokaż surowy HTML",
        "expand_templates_preview": "Podgląd",
-       "expand_templates_preview_fail_html": "<em>Ponieważ {{SITENAME}} ma włączony surowy kod HTML i zaistniała strata danych z sesji, podgląd jest ukryty jako zabezpieczenie przed atakiem JavaScript.</em>\n\n<strong>Jeśli to jest próba słusznego podglądu, proszę spróbować ponownie.</strong>\nJeśli to nadal nie działa, spróbuj [[Special:UserLogout|wylogować się]] i zalogować się z powrotem.",
+       "expand_templates_preview_fail_html": "<em>Ponieważ {{SITENAME}} ma włączony surowy kod HTML i zaistniała strata danych z sesji, podgląd jest ukryty jako zabezpieczenie przed atakiem JavaScript.</em>\n\n<strong>Jeśli to jest próba słusznego podglądu, proszę spróbować ponownie.</strong>\nJeśli to nie pomoże – spróbuj [[Special:UserLogout|wylogować się]] i zalogować ponownie, a także upewnij się, że twoja przeglądarka akceptuje ciasteczka z tej witryny.",
        "pagelanguage": "Zmiana języka strony",
        "pagelang-name": "Strona",
        "pagelang-language": "Język",
index c9a0c23..b388053 100644 (file)
        "exif-make": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nThe Manufacturer of the digital camera (or scanner) that took the photo.",
        "exif-model": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nThe model of camera (or scanner) used to take the picture.",
        "exif-software": "Short for \"The software which was used to create or modify this image\".\n\nThe property can come from the Exif Software tag, PNG software chunk, iptc-iim 2:65 Software field, or XMP's xmp:CreatorTool field.\n\nExif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].",
-       "exif-artist": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nThis message labels the author or artist of the work. Usually this means who took the photograph, or who drew the picture. The corresponding value field most commonly contains a single author, however it can contain an ordered (or unordered depending on which metadata standard is used to store the information) list of authors. Sometimes the persons position is prefixed before their name such as \"Photographer, John Smith\". The exif standard recommends multiple authors be specified by \"position, Author 1; position for author 2, Author 2's name\" however this doesn't seem to happen in practise very often. If multiple authors are specified using a non-exif standard, then a billeted (or numbered) list is used.\n\nThis property can be specified by exif Artist tag, XMP's tiff:Artist, XMP's dc:creator, iptc-iim's 2:80 byline, PNG's author textual chunk, PNG's (unofficial) artist textual chunk. XMP's photoshop:AuthorsPosition and iptc 2:85 byline-title can also affect display of this property.\n{{Identical|Author}}",
+       "exif-artist": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nThis message labels the author or artist of the work. Usually this means who took the photograph, or who drew the picture. The corresponding value field most commonly contains a single author, however it can contain an ordered (or unordered depending on which metadata standard is used to store the information) list of authors. Sometimes the persons position is prefixed before their name such as \"Photographer, John Smith\". The exif standard recommends multiple authors be specified by \"position, Author 1; position for author 2, Author 2's name\" however this doesn't seem to happen in practice very often. If multiple authors are specified using a non-exif standard, then a billeted (or numbered) list is used.\n\nThis property can be specified by exif Artist tag, XMP's tiff:Artist, XMP's dc:creator, iptc-iim's 2:80 byline, PNG's author textual chunk, PNG's (unofficial) artist textual chunk. XMP's photoshop:AuthorsPosition and iptc 2:85 byline-title can also affect display of this property.\n{{Identical|Author}}",
        "exif-copyright": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nLabel for information contained in exif Copyright tag, XMP dc:rights, IPTC-iim 2:116, or PNG copyright textual chunk.\n\nTypically the copyright statement for the photograph/drawing/video (such as ''(c) 2010 John Smith. Released under GFDL''). Sometimes contains license information. See also {{msg-mw|exif-copyrightowner}}",
        "exif-exifversion": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nVersion of exif standard photo uses. Typically this is 2.22",
        "exif-flashpixversion": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nVersion of flashpix used. Flashpix is a format used for storing some types of metadata in image. It is not as commonly used as EXIF, and mediawiki currently cannot read Flashpix data.",
        "exif-compressedbitsperpixel": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].",
        "exif-pixelydimension": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n{{Identical|Image width}}",
        "exif-pixelxdimension": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n{{Identical|Image height}}",
-       "exif-usercomment": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nComments by user. Sometimes used like ImageDescription when the ImageDescription contained non-ascii characters. (Technically ImageDescription is supposed to contain ascii characters. In practise utf-8 is used in ImageDescription, so this field isn't used too much.)",
+       "exif-usercomment": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nComments by user. Sometimes used like ImageDescription when the ImageDescription contained non-ascii characters. (Technically ImageDescription is supposed to contain ascii characters. In practice utf-8 is used in ImageDescription, so this field isn't used too much.)",
        "exif-relatedsoundfile": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nSome cameras offer the option to record an audio \"memo\" for the photo they just took. If the user did that, the name of the file is labelled with this message.",
        "exif-datetimeoriginal": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nThe date and time when the original image data was generated. For example if it was a painting from 1773, scanned in to a computer in 2007, the datetimeoriginal would be 1773 and {{msg-mw|exif-datetimedigitized}} would have the 2007 date.",
        "exif-datetimedigitized": "Exif is a format for storing metadata in image files. See this [[w:Exchangeable_image_file_format|Wikipedia article]] and the example at the bottom of [[commons:File:Phalacrocorax-auritus-020.jpg|this page on Commons]]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].\n\nThe date and time when the image was stored as digital data.",
index 1571bc2..4274b07 100644 (file)
        "changecontentmodel-nodirectediting": "Model vsebine $1 ne podpira neposrednega urejanja",
        "log-name-contentmodel": "Dnevnik sprememb modela vsebine",
        "log-description-contentmodel": "Dogodki, povezani z modeli vsebin strani",
+       "logentry-contentmodel-new": "$1 je {{GENDER:$2|ustvaril|ustvarila|ustvaril(-a)}} stran $3 z neprivzetim modelom vsebine »$5«",
        "logentry-contentmodel-change": "$1 je {{GENDER:$2|spremenil|spremenila|spremenil(-a)}} model vsebine strani $3 z »$4« na »$5«",
        "logentry-contentmodel-change-revertlink": "vrni",
        "logentry-contentmodel-change-revert": "vrni",
index 849f526..e856378 100644 (file)
        "newarticle": "(Ny)",
        "newarticletext": "Du har klickat på en länk till en sida som inte finns ännu. För att skapa sidan, börja att skriva i fältet nedan (du kan läsa mer på [$1 hjälpsidan]). Om du kom hit av misstag kan du bara trycka på <strong>tillbaka</strong>-knappen i din webbläsare.",
        "anontalkpagetext": "----''Detta är diskussionssidan för en anonym användare som inte ännu skapat ett konto, eller som inte använder det.\nDärför måste vi använda den numeriska IP-adressen för att identifiera honom/henne.\nEn sådan IP-adress kan delas av flera användare.\nOm du är en anonym användare och känner att irrelevanta kommentarer har riktats mot dig, vänligen [[Special:UserLogin/signup|skapa ett konto]] eller [[Special:UserLogin|logga in]] för att undvika framtida förväxlingar med andra anonyma användare.''",
-       "noarticletext": "Det finns just nu ingen text på denna sida.\nDu kan [[Special:Search/{{PAGENAME}}|söka efter denna sidtitel]] på andra sidor, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söka i relaterade loggar], eller [{{fullurl:{{FULLPAGENAME}}|action=edit}} redigera denna sida]</span>.",
+       "noarticletext": "Det finns just nu ingen text på denna sida.\nDu kan [[Special:Search/{{PAGENAME}}|söka efter denna sidtitel]] på andra sidor, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söka i relaterade loggar], eller [{{fullurl:{{FULLPAGENAME}}|action=edit}} skapa denna sida]</span>.",
        "noarticletext-nopermission": "Det finns för tillfället ingen text på denna sida.\nDu kan [[Special:Search/{{PAGENAME}}|söka efter denna sidas titel]] på andra sidor,\neller <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söka i relaterade loggar]</span> men du har inte behörighet att skapa sidan.",
        "missing-revision": "Revisionen #$1 av sidan med namnet \"{{FULLPAGENAME}}\" finns inte.\n\nDetta orsakas vanligen av efter en gammal historiklänk till en sida som har raderats.\nDetaljer kan hittas i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} raderingsloggen].",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" är inte ett registrerat användarkonto. Tänk efter om du vill skapa/redigera den här sidan.",
        "recentchanges-label-bot": "Denna redigering gjordes av en bot",
        "recentchanges-label-unpatrolled": "Denna redigering har inte blivit patrullerad ännu",
        "recentchanges-label-plusminus": "Sidans storlek ändrades med detta antal byte",
-       "recentchanges-legend-heading": "'''Teckenförklaring:'''",
+       "recentchanges-legend-heading": "<strong>Teckenförklaring:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (se även [[Special:NewPages|listan över nya sidor]])",
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Visa",
        "backend-fail-read": "Kunde inte läsa filen ''$1''.",
        "backend-fail-create": "Kunde inte skapa filen $1.",
        "backend-fail-maxsize": "Kunde inte skapa filen $1 eftersom den är större än {{PLURAL:$2|en byte|$2 bytes}}.",
-       "backend-fail-readonly": "Lagringssystemet \"$1\" är för närvarande skrivskyddad. Den angivna anledningen är: \"$2\"",
+       "backend-fail-readonly": "Lagringssystemet \"$1\" är för närvarande skrivskyddat. Den angivna anledningen var: <em>$2</em>",
        "backend-fail-synced": "Filen \"$1\" är i ett inkonsekvent tillstånd inom de interna lagringssystemen",
        "backend-fail-connect": "Kunde inte ansluta till lagringssystemet \"$1\".",
        "backend-fail-internal": "Ett okänt fel uppstod i lagringssystemet \"$1\".",
        "delete-toobig": "Denna sida har en lång redigeringshistorik med mer än $1 {{PLURAL:$1|sidversion|sidversioner}}. Borttagning av sådana sidor har begränsats för att förhindra oavsiktliga driftstörningar på {{SITENAME}}.",
        "delete-warning-toobig": "Denna sida har en lång redigeringshistorik med mer än $1 {{PLURAL:$1|sidversion|sidversioner}}. Att radera sidan kan skapa problem med hanteringen av databasen på {{SITENAME}}; var försiktig.",
        "deleteprotected": "Du kan inte radera denna sida eftersom den är skyddad.",
-       "deleting-backlinks-warning": "'''Varning:'''\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|Andra sidor]] länkar till eller inkluderar sidan som du är på väg att radera.",
+       "deleting-backlinks-warning": "<strong>Varning:</strong>\n[[Special:WhatLinksHere/{{FULLPAGENAME}}|Andra sidor]] länkar till eller inkluderar sidan som du är på väg att radera.",
        "rollback": "Rulla tillbaka ändringar",
        "rollbacklink": "rulla tillbaka",
        "rollbacklinkcount": "rulla tillbaka $1 {{PLURAL:$1|redigering|redigeringar}}",
        "changecontentmodel-nodirectediting": "Innehållsmodellen $1 stöder inte direkt redigering",
        "log-name-contentmodel": "Ändringslogg för innehållsmodellen",
        "log-description-contentmodel": "Händelser som är relaterade till en sidas innehållsmodeller",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|skapade}} sidan $3 med den icke-standardiserade innehållsmodellen \"$5\"",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|ändrade}} innehållsmodellen för sidan $3 från \"$4\" till \"$5\"",
        "logentry-contentmodel-change-revertlink": "återställ",
        "logentry-contentmodel-change-revert": "återställ",
        "tags-edit-revision-legend": "Lägg eller ta bort märken från {{PLURAL:$1|denna sidversion|alla $1 sidversioner}}",
        "tags-edit-logentry-legend": "Lägg till eller ta bort märken från {{PLURAL:$1|denna loggpost|alla $1 loggposter}}",
        "tags-edit-existing-tags": "Befintliga märken:",
-       "tags-edit-existing-tags-none": "''Inga''",
+       "tags-edit-existing-tags-none": "<em>Inga</em>",
        "tags-edit-new-tags": "Nya märken:",
        "tags-edit-add": "Lägg till dessa märken:",
        "tags-edit-remove": "Ta bort dessa märken:",
index bfe89bd..e5f2d1d 100644 (file)
        "rcshowhidemine": "Benim değişikliklerimi $1",
        "rcshowhidemine-show": "göster",
        "rcshowhidemine-hide": "gizle",
-       "rcshowhidecategorization": "$1 Sayfa kategorizasyonu",
+       "rcshowhidecategorization": "$1 sayfa kategorizasyonu",
        "rcshowhidecategorization-show": "Göster",
        "rcshowhidecategorization-hide": "Gizle",
        "rclinks": "Son $2 günde yapılan son $1 değişikliği göster;<br /> $3",
        "wlshowhideanons": "anonim kullanıcılar",
        "wlshowhidepatr": "incelenmiş değişiklikler",
        "wlshowhidemine": "değişikliklerim",
+       "wlshowhidecategorization": "sayfa kategorizasyonu",
        "watchlist-options": "İzleme listesi seçenekleri",
        "watching": "İzleniyor...",
        "unwatching": "İzlenmiyor...",
index f36eb07..2d33eb1 100644 (file)
        "changecontentmodel-nodirectediting": "Модель вмісту $1 не підтримує пряме редагування",
        "log-name-contentmodel": "Журнал змін моделі вмісту",
        "log-description-contentmodel": "Події, пов'язані з моделями вмісту сторінки",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|створив|створила}} сторінку $3, використовуючи нестандартну модель вмісту «$5»",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|змінив|змінила}} модель вмісту сторінки $3 з «$4» на «$5»",
        "logentry-contentmodel-change-revertlink": "відкинути",
        "logentry-contentmodel-change-revert": "відкинути",
index 8651a68..5fab082 100644 (file)
@@ -87,7 +87,7 @@ abstract class Benchmarker extends Maintenance {
                        $ret .= sprintf( "%s times: function %s(%s) :\n",
                                $res['count'],
                                $res['function'],
-                               join( ', ', $res['arguments'] )
+                               implode( ', ', $res['arguments'] )
                        );
                        $ret .= sprintf( "   %6.2fms (%6.2fms each)\n",
                                $res['delta'] * 1000,
index 6bc9a6a..3c679e6 100644 (file)
@@ -53,7 +53,7 @@ class GetConfiguration extends Maintenance {
                $this->addOption( 'regex', 'regex to filter variables with', false, true );
                $this->addOption( 'iregex', 'same as --regex but case insensitive', false, true );
                $this->addOption( 'settings', 'Space-separated list of wg* variables', false, true );
-               $this->addOption( 'format', join( ', ', self::$outFormats ), false, true );
+               $this->addOption( 'format', implode( ', ', self::$outFormats ), false, true );
        }
 
        protected function validateParamsAndArgs() {
index 7e62b89..b2530ce 100644 (file)
@@ -63,6 +63,9 @@ class InitSiteStats extends Maintenance {
                        $this->output( "\nUpdating site statistics..." );
                        $counter->refresh();
                        $this->output( "done.\n" );
+               } else {
+                       $this->output( "\nTo update the site statistics table, run the script "
+                               . "with the --update option.\n" );
                }
 
                if ( $this->hasOption( 'active' ) ) {
diff --git a/tests/phpunit/includes/WatchedItemIntegrationTest.php b/tests/phpunit/includes/WatchedItemIntegrationTest.php
new file mode 100644 (file)
index 0000000..20fcedb
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @group Database
+ *
+ * @covers WatchedItem
+ */
+class WatchedItemIntegrationTest extends MediaWikiTestCase {
+
+       public function setUp() {
+               parent::setUp();
+               self::$users['WatchedItemIntegrationTestUser']
+                       = new TestUser( 'WatchedItemIntegrationTestUser' );
+       }
+
+       private function getUser() {
+               return self::$users['WatchedItemIntegrationTestUser']->getUser();
+       }
+
+       public function testWatchAndUnWatchItem() {
+               $user = $this->getUser();
+               $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
+               // Cleanup after previous tests
+               WatchedItem::fromUserTitle( $user, $title )->removeWatch();
+
+               $this->assertFalse(
+                       WatchedItem::fromUserTitle( $user, $title )->isWatched(),
+                       'Page should not initially be watched'
+               );
+               WatchedItem::fromUserTitle( $user, $title )->addWatch();
+               $this->assertTrue(
+                       WatchedItem::fromUserTitle( $user, $title )->isWatched(),
+                       'Page should be watched'
+               );
+               WatchedItem::fromUserTitle( $user, $title )->removeWatch();
+               $this->assertFalse(
+                       WatchedItem::fromUserTitle( $user, $title )->isWatched(),
+                       'Page should be unwatched'
+               );
+       }
+
+       public function testUpdateAndResetNotificationTimestamp() {
+               $user = $this->getUser();
+               $otherUser = ( new TestUser( 'WatchedItemIntegrationTestUser_otherUser' ) )->getUser();
+               $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
+               WatchedItem::fromUserTitle( $user, $title )->addWatch();
+               $this->assertNull( WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp() );
+
+               EmailNotification::updateWatchlistTimestamp( $otherUser, $title, '20150202010101' );
+               $this->assertEquals(
+                       '20150202010101',
+                       WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp()
+               );
+
+               WatchedItem::fromUserTitle( $user, $title )->resetNotificationTimestamp();
+               $this->assertNull( WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp() );
+       }
+
+       public function testDuplicateAllAssociatedEntries() {
+               $user = $this->getUser();
+               $titleOld = Title::newFromText( 'WatchedItemIntegrationTestPageOld' );
+               $titleNew = Title::newFromText( 'WatchedItemIntegrationTestPageNew' );
+               WatchedItem::fromUserTitle( $user, $titleOld->getSubjectPage() )->addWatch();
+               WatchedItem::fromUserTitle( $user, $titleOld->getTalkPage() )->addWatch();
+               // Cleanup after previous tests
+               WatchedItem::fromUserTitle( $user, $titleNew->getSubjectPage() )->removeWatch();
+               WatchedItem::fromUserTitle( $user, $titleNew->getTalkPage() )->removeWatch();
+
+               WatchedItem::duplicateEntries( $titleOld, $titleNew );
+
+               $this->assertTrue(
+                       WatchedItem::fromUserTitle( $user, $titleOld->getSubjectPage() )->isWatched()
+               );
+               $this->assertTrue(
+                       WatchedItem::fromUserTitle( $user, $titleOld->getTalkPage() )->isWatched()
+               );
+               $this->assertTrue(
+                       WatchedItem::fromUserTitle( $user, $titleNew->getSubjectPage() )->isWatched()
+               );
+               $this->assertTrue(
+                       WatchedItem::fromUserTitle( $user, $titleNew->getTalkPage() )->isWatched()
+               );
+       }
+
+       public function testIsWatched_falseOnNotAllowed() {
+               $user = $this->getUser();
+               $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
+               WatchedItem::fromUserTitle( $user, $title )->addWatch();
+
+               $this->assertTrue( WatchedItem::fromUserTitle( $user, $title )->isWatched() );
+               $user->mRights = [];
+               $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->isWatched() );
+       }
+
+       public function testGetNotificationTimestamp_falseOnNotAllowed() {
+               $user = $this->getUser();
+               $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
+               WatchedItem::fromUserTitle( $user, $title )->addWatch();
+               WatchedItem::fromUserTitle( $user, $title )->resetNotificationTimestamp();
+
+               $this->assertEquals(
+                       null,
+                       WatchedItem::fromUserTitle( $user, $title )->getNotificationTimestamp()
+               );
+               $user->mRights = [];
+               $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->isWatched() );
+       }
+
+       public function testRemoveWatch_falseOnNotAllowed() {
+               $user = $this->getUser();
+               $title = Title::newFromText( 'WatchedItemIntegrationTestPage' );
+               WatchedItem::fromUserTitle( $user, $title )->addWatch();
+
+               $previousRights = $user->mRights;
+               $user->mRights = [];
+               $this->assertFalse( WatchedItem::fromUserTitle( $user, $title )->removeWatch() );
+               $user->mRights = $previousRights;
+               $this->assertTrue( WatchedItem::fromUserTitle( $user, $title )->removeWatch() );
+       }
+
+}
diff --git a/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php b/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
new file mode 100644 (file)
index 0000000..9341fb8
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @group Database
+ *
+ * @covers WatchedItemStore
+ */
+class WatchedItemStoreIntegrationTest extends MediaWikiTestCase {
+
+       public function setUp() {
+               parent::setUp();
+               self::$users['WatchedItemStoreIntegrationTestUser']
+                       = new TestUser( 'WatchedItemStoreIntegrationTestUser' );
+       }
+
+       private function getUser() {
+               return self::$users['WatchedItemStoreIntegrationTestUser']->getUser();
+       }
+
+       public function testWatchAndUnWatchItem() {
+               $user = $this->getUser();
+               $title = Title::newFromText( 'WatchedItemStoreIntegrationTestPage' );
+               $store = WatchedItemStore::getDefaultInstance();
+               // Cleanup after previous tests
+               $store->removeWatch( $user, $title );
+
+               $this->assertFalse(
+                       $store->isWatched( $user, $title ),
+                       'Page should not initially be watched'
+               );
+               $store->addWatch( $user, $title );
+               $this->assertTrue(
+                       $store->isWatched( $user, $title ),
+                       'Page should be watched'
+               );
+               $store->removeWatch( $user, $title );
+               $this->assertFalse(
+                       $store->isWatched( $user, $title ),
+                       'Page should be unwatched'
+               );
+       }
+
+       public function testUpdateAndResetNotificationTimestamp() {
+               $user = $this->getUser();
+               $otherUser = ( new TestUser( 'WatchedItemStoreIntegrationTestUser_otherUser' ) )->getUser();
+               $title = Title::newFromText( 'WatchedItemStoreIntegrationTestPage' );
+               $store = WatchedItemStore::getDefaultInstance();
+               $store->addWatch( $user, $title );
+               $this->assertNull( $store->loadWatchedItem( $user, $title )->getNotificationTimestamp() );
+
+               $store->updateNotificationTimestamp( $otherUser, $title, '20150202010101' );
+               $this->assertEquals(
+                       '20150202010101',
+                       $store->loadWatchedItem( $user, $title )->getNotificationTimestamp()
+               );
+
+               $this->assertTrue( $store->resetNotificationTimestamp( $user, $title ) );
+               $this->assertNull( $store->loadWatchedItem( $user, $title )->getNotificationTimestamp() );
+       }
+
+       public function testDuplicateAllAssociatedEntries() {
+               $user = $this->getUser();
+               $titleOld = Title::newFromText( 'WatchedItemStoreIntegrationTestPageOld' );
+               $titleNew = Title::newFromText( 'WatchedItemStoreIntegrationTestPageNew' );
+               $store = WatchedItemStore::getDefaultInstance();
+               $store->addWatch( $user, $titleOld->getSubjectPage() );
+               $store->addWatch( $user, $titleOld->getTalkPage() );
+               // Cleanup after previous tests
+               $store->removeWatch( $user, $titleNew->getSubjectPage() );
+               $store->removeWatch( $user, $titleNew->getTalkPage() );
+
+               $store->duplicateAllAssociatedEntries( $titleOld, $titleNew );
+
+               $this->assertTrue( $store->isWatched( $user, $titleOld->getSubjectPage() ) );
+               $this->assertTrue( $store->isWatched( $user, $titleOld->getTalkPage() ) );
+               $this->assertTrue( $store->isWatched( $user, $titleNew->getSubjectPage() ) );
+               $this->assertTrue( $store->isWatched( $user, $titleNew->getTalkPage() ) );
+       }
+
+}
diff --git a/tests/phpunit/includes/WatchedItemStoreTest.php b/tests/phpunit/includes/WatchedItemStoreTest.php
deleted file mode 100644 (file)
index fc132b0..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-/**
- * @author Addshore
- *
- * @covers WatchedItemStore
- */
-class WatchedItemStoreTest extends PHPUnit_Framework_TestCase {
-
-       /**
-        * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
-        */
-       private function getMockDb() {
-               return $this->getMock( 'IDatabase' );
-       }
-
-       /**
-        * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
-        */
-       private function getMockLoadBalancer( $mockDb ) {
-               $mock = $this->getMockBuilder( 'LoadBalancer' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
-               $mock->expects( $this->any() )
-                       ->method( 'getConnection' )
-                       ->will( $this->returnValue( $mockDb ) );
-               return $mock;
-       }
-
-       private function getFakeRow( $userId, $timestamp ) {
-               $fakeRow = new stdClass();
-               $fakeRow->wl_user = $userId;
-               $fakeRow->wl_notificationtimestamp = $timestamp;
-               return $fakeRow;
-       }
-
-       public function testDuplicateEntry_nothingToDuplicate() {
-               $mockDb = $this->getMockDb();
-               $mockDb->expects( $this->exactly( 1 ) )
-                       ->method( 'select' )
-                       ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
-
-               $store = new WatchedItemStore( $this->getMockLoadBalancer( $mockDb ) );
-
-               $store->duplicateEntry(
-                       Title::newFromText( 'Old_Title' ),
-                       Title::newFromText( 'New_Title' )
-               );
-       }
-
-       public function testDuplicateEntry_somethingToDuplicate() {
-               $fakeRows = [
-                       $this->getFakeRow( 1, '20151212010101' ),
-                       $this->getFakeRow( 2, null ),
-               ];
-
-               $mockDb = $this->getMockDb();
-               $mockDb->expects( $this->at( 0 ) )
-                       ->method( 'select' )
-                       ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
-               $mockDb->expects( $this->at( 1 ) )
-                       ->method( 'replace' )
-                       ->with(
-                               'watchlist',
-                               [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
-                               [
-                                       [
-                                               'wl_user' => 1,
-                                               'wl_namespace' => 0,
-                                               'wl_title' => 'New_Title',
-                                               'wl_notificationtimestamp' => '20151212010101',
-                                       ],
-                                       [
-                                               'wl_user' => 2,
-                                               'wl_namespace' => 0,
-                                               'wl_title' => 'New_Title',
-                                               'wl_notificationtimestamp' => null,
-                                       ],
-                               ],
-                               $this->isType( 'string' )
-                       );
-
-               $store = new WatchedItemStore( $this->getMockLoadBalancer( $mockDb ) );
-
-               $store->duplicateEntry(
-                       Title::newFromText( 'Old_Title' ),
-                       Title::newFromText( 'New_Title' )
-               );
-       }
-
-}
diff --git a/tests/phpunit/includes/WatchedItemStoreUnitTest.php b/tests/phpunit/includes/WatchedItemStoreUnitTest.php
new file mode 100644 (file)
index 0000000..709b4b4
--- /dev/null
@@ -0,0 +1,1236 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @covers WatchedItemStore
+ */
+class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
+
+       /**
+        * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
+        */
+       private function getMockDb() {
+               return $this->getMock( IDatabase::class );
+       }
+
+       /**
+        * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
+        */
+       private function getMockLoadBalancer( $mockDb ) {
+               $mock = $this->getMockBuilder( LoadBalancer::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getConnection' )
+                       ->will( $this->returnValue( $mockDb ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getReadOnlyReason' )
+                       ->will( $this->returnValue( false ) );
+               return $mock;
+       }
+
+       /**
+        * @return PHPUnit_Framework_MockObject_MockObject|BagOStuff
+        */
+       private function getMockCache() {
+               $mock = $this->getMockBuilder( BagOStuff::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'makeKey' )
+                       ->will( $this->returnCallback( function() {
+                               return implode( ':', func_get_args() );
+                       } ) );
+               return $mock;
+       }
+
+       /**
+        * @param int $id
+        * @return PHPUnit_Framework_MockObject_MockObject|User
+        */
+       private function getMockNonAnonUserWithId( $id ) {
+               $mock = $this->getMock( User::class );
+               $mock->expects( $this->any() )
+                       ->method( 'isAnon' )
+                       ->will( $this->returnValue( false ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getId' )
+                       ->will( $this->returnValue( $id ) );
+               return $mock;
+       }
+
+       /**
+        * @return User
+        */
+       private function getAnonUser() {
+               return User::newFromName( 'Anon_User' );
+       }
+
+       private function getFakeRow( array $rowValues ) {
+               $fakeRow = new stdClass();
+               foreach ( $rowValues as $valueName => $value ) {
+                       $fakeRow->$valueName = $value;
+               }
+               return $fakeRow;
+       }
+
+       public function testGetDefaultInstance() {
+               $instanceOne = WatchedItemStore::getDefaultInstance();
+               $instanceTwo = WatchedItemStore::getDefaultInstance();
+               $this->assertSame( $instanceOne, $instanceTwo );
+       }
+
+       public function testDuplicateEntry_nothingToDuplicate() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user',
+                                       'wl_notificationtimestamp',
+                               ],
+                               [
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'Old_Title',
+                               ],
+                               'WatchedItemStore::duplicateEntry',
+                               [ 'FOR UPDATE' ]
+                       )
+                       ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       new HashBagOStuff( [ 'maxKeys' => 100 ] )
+               );
+
+               $store->duplicateEntry(
+                       Title::newFromText( 'Old_Title' ),
+                       Title::newFromText( 'New_Title' )
+               );
+       }
+
+       public function testDuplicateEntry_somethingToDuplicate() {
+               $fakeRows = [
+                       $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
+                       $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
+               ];
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->at( 0 ) )
+                       ->method( 'select' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user',
+                                       'wl_notificationtimestamp',
+                               ],
+                               [
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'Old_Title',
+                               ]
+                       )
+                       ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
+               $mockDb->expects( $this->at( 1 ) )
+                       ->method( 'replace' )
+                       ->with(
+                               'watchlist',
+                               [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
+                               [
+                                       [
+                                               'wl_user' => 1,
+                                               'wl_namespace' => 0,
+                                               'wl_title' => 'New_Title',
+                                               'wl_notificationtimestamp' => '20151212010101',
+                                       ],
+                                       [
+                                               'wl_user' => 2,
+                                               'wl_namespace' => 0,
+                                               'wl_title' => 'New_Title',
+                                               'wl_notificationtimestamp' => null,
+                                       ],
+                               ],
+                               $this->isType( 'string' )
+                       );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       new HashBagOStuff( [ 'maxKeys' => 100 ] )
+               );
+
+               $store->duplicateEntry(
+                       Title::newFromText( 'Old_Title' ),
+                       Title::newFromText( 'New_Title' )
+               );
+       }
+
+       public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->at( 0 ) )
+                       ->method( 'select' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user',
+                                       'wl_notificationtimestamp',
+                               ],
+                               [
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'Old_Title',
+                               ]
+                       )
+                       ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
+               $mockDb->expects( $this->at( 1 ) )
+                       ->method( 'select' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user',
+                                       'wl_notificationtimestamp',
+                               ],
+                               [
+                                       'wl_namespace' => 1,
+                                       'wl_title' => 'Old_Title',
+                               ]
+                       )
+                       ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       new HashBagOStuff( [ 'maxKeys' => 100 ] )
+               );
+
+               $store->duplicateAllAssociatedEntries(
+                       Title::newFromText( 'Old_Title' ),
+                       Title::newFromText( 'New_Title' )
+               );
+       }
+
+       public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
+               $fakeRows = [
+                       $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
+               ];
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->at( 0 ) )
+                       ->method( 'select' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user',
+                                       'wl_notificationtimestamp',
+                               ],
+                               [
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'Old_Title',
+                               ]
+                       )
+                       ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
+               $mockDb->expects( $this->at( 1 ) )
+                       ->method( 'replace' )
+                       ->with(
+                               'watchlist',
+                               [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
+                               [
+                                       [
+                                               'wl_user' => 1,
+                                               'wl_namespace' => 0,
+                                               'wl_title' => 'New_Title',
+                                               'wl_notificationtimestamp' => '20151212010101',
+                                       ],
+                               ],
+                               $this->isType( 'string' )
+                       );
+               $mockDb->expects( $this->at( 2 ) )
+                       ->method( 'select' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user',
+                                       'wl_notificationtimestamp',
+                               ],
+                               [
+                                       'wl_namespace' => 1,
+                                       'wl_title' => 'Old_Title',
+                               ]
+                       )
+                       ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
+               $mockDb->expects( $this->at( 3 ) )
+                       ->method( 'replace' )
+                       ->with(
+                               'watchlist',
+                               [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
+                               [
+                                       [
+                                               'wl_user' => 1,
+                                               'wl_namespace' => 1,
+                                               'wl_title' => 'New_Title',
+                                               'wl_notificationtimestamp' => '20151212010101',
+                                       ],
+                               ],
+                               $this->isType( 'string' )
+                       );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       new HashBagOStuff( [ 'maxKeys' => 100 ] )
+               );
+
+               $store->duplicateAllAssociatedEntries(
+                       Title::newFromText( 'Old_Title' ),
+                       Title::newFromText( 'New_Title' )
+               );
+       }
+
+       public function testAddWatch_nonAnonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'insert' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       [
+                                               'wl_user' => 1,
+                                               'wl_namespace' => 0,
+                                               'wl_title' => 'Some_Page',
+                                               'wl_notificationtimestamp' => null,
+                                       ]
+                               ]
+                       );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'delete' )
+                       ->with( '0:Some_Page:1' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $store->addWatch(
+                       $this->getMockNonAnonUserWithId( 1 ),
+                       Title::newFromText( 'Some_Page' )
+               );
+       }
+
+       public function testAddWatch_anonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'insert' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $store->addWatch(
+                       $this->getAnonUser(),
+                       Title::newFromText( 'Some_Page' )
+               );
+       }
+
+       public function testAddWatchBatch_nonAnonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'insert' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       [
+                                               'wl_user' => 1,
+                                               'wl_namespace' => 0,
+                                               'wl_title' => 'Some_Page',
+                                               'wl_notificationtimestamp' => null,
+                                       ],
+                                       [
+                                               'wl_user' => 1,
+                                               'wl_namespace' => 1,
+                                               'wl_title' => 'Some_Page',
+                                               'wl_notificationtimestamp' => null,
+                                       ]
+                               ]
+                       );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->exactly( 2 ) )
+                       ->method( 'delete' );
+               $mockCache->expects( $this->at( 1 ) )
+                       ->method( 'delete' )
+                       ->with( '0:Some_Page:1' );
+               $mockCache->expects( $this->at( 3 ) )
+                       ->method( 'delete' )
+                       ->with( '1:Some_Page:1' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $mockUser = $this->getMockNonAnonUserWithId( 1 );
+
+               $this->assertTrue(
+                       $store->addWatchBatch(
+                               [
+                                       [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
+                                       [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
+                               ]
+                       )
+               );
+       }
+
+       public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'insert' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       [
+                                               'wl_user' => 1,
+                                               'wl_namespace' => 0,
+                                               'wl_title' => 'Some_Page',
+                                               'wl_notificationtimestamp' => null,
+                                       ]
+                               ]
+                       );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'delete' )
+                       ->with( '0:Some_Page:1' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertTrue(
+                       $store->addWatchBatch(
+                               [
+                                       [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
+                                       [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
+                               ]
+                       )
+               );
+       }
+
+       public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'insert' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $anonUser = $this->getAnonUser();
+               $this->assertFalse(
+                       $store->addWatchBatch(
+                               [
+                                       [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
+                                       [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
+                               ]
+                       )
+               );
+       }
+
+       public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'insert' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->addWatchBatch( [] )
+               );
+       }
+
+       public function testLoadWatchedItem_existingItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue(
+                               $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+                       ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'set' )
+                       ->with(
+                               '0:SomeDbKey:1'
+                       );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $watchedItem = $store->loadWatchedItem(
+                       $this->getMockNonAnonUserWithId( 1 ),
+                       new TitleValue( 0, 'SomeDbKey' )
+               );
+               $this->assertInstanceOf( 'WatchedItem', $watchedItem );
+               $this->assertEquals( 1, $watchedItem->getUser()->getId() );
+               $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
+               $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
+       }
+
+       public function testLoadWatchedItem_noItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->loadWatchedItem(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testLoadWatchedItem_anonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'selectRow' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->loadWatchedItem(
+                               $this->getAnonUser(),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testRemoveWatch_existingItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'delete' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       );
+               $mockDb->expects( $this->once() )
+                       ->method( 'affectedRows' )
+                       ->will( $this->returnValue( 1 ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'delete' )
+                       ->with( '0:SomeDbKey:1' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertTrue(
+                       $store->removeWatch(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testRemoveWatch_noItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'delete' )
+                       ->with(
+                               'watchlist',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       );
+               $mockDb->expects( $this->once() )
+                       ->method( 'affectedRows' )
+                       ->will( $this->returnValue( 0 ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'delete' )
+                       ->with( '0:SomeDbKey:1' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->removeWatch(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testRemoveWatch_anonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->removeWatch(
+                               $this->getAnonUser(),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testGetWatchedItem_existingItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue(
+                               $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+                       ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'get' )
+                       ->with(
+                               '0:SomeDbKey:1'
+                       )
+                       ->will( $this->returnValue( null ) );
+               $mockCache->expects( $this->once() )
+                       ->method( 'set' )
+                       ->with(
+                               '0:SomeDbKey:1'
+                       );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $watchedItem = $store->getWatchedItem(
+                       $this->getMockNonAnonUserWithId( 1 ),
+                       new TitleValue( 0, 'SomeDbKey' )
+               );
+               $this->assertInstanceOf( 'WatchedItem', $watchedItem );
+               $this->assertEquals( 1, $watchedItem->getUser()->getId() );
+               $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
+               $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
+       }
+
+       public function testGetWatchedItem_cachedItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'selectRow' );
+
+               $mockUser = $this->getMockNonAnonUserWithId( 1 );
+               $linkTarget = new TitleValue( 0, 'SomeDbKey' );
+               $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'get' )
+                       ->with(
+                               '0:SomeDbKey:1'
+                       )
+                       ->will( $this->returnValue( $cachedItem ) );
+               $mockCache->expects( $this->never() )
+                       ->method( 'set' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertEquals(
+                       $cachedItem,
+                       $store->getWatchedItem(
+                               $mockUser,
+                               $linkTarget
+                       )
+               );
+       }
+
+       public function testGetWatchedItem_noItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'set' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->getWatchedItem(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testGetWatchedItem_anonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'selectRow' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'set' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->getWatchedItem(
+                               $this->getAnonUser(),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testIsWatchedItem_existingItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue(
+                               $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+                       ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'set' )
+                       ->with(
+                               '0:SomeDbKey:1'
+                       );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertTrue(
+                       $store->isWatched(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testIsWatchedItem_noItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'set' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->isWatched(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testIsWatchedItem_anonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'selectRow' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'set' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->isWatched(
+                               $this->getAnonUser(),
+                               new TitleValue( 0, 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testResetNotificationTimestamp_anonymousUser() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'selectRow' );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'set' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->resetNotificationTimestamp(
+                               $this->getAnonUser(),
+                               Title::newFromText( 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testResetNotificationTimestamp_noItem() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue( [] ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->never() )
+                       ->method( 'set' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               $this->assertFalse(
+                       $store->resetNotificationTimestamp(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               Title::newFromText( 'SomeDbKey' )
+                       )
+               );
+       }
+
+       public function testResetNotificationTimestamp_item() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $title = Title::newFromText( 'SomeDbKey' );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue(
+                               $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+                       ) );
+
+               $mockCache = $this->getMockCache();
+               $mockCache->expects( $this->once() )
+                       ->method( 'set' )
+                       ->with(
+                               '0:SomeDbKey:1',
+                               $this->isInstanceOf( WatchedItem::class )
+                       );
+               $mockCache->expects( $this->once() )
+                       ->method( 'delete' )
+                       ->with( '0:SomeDbKey:1' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               // Note: This does not actually assert the job is correct
+               $callableCallCounter = 0;
+               $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
+                       $callableCallCounter++;
+                       $this->assertInternalType( 'callable', $callable );
+               };
+               $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
+
+               $this->assertTrue(
+                       $store->resetNotificationTimestamp(
+                               $user,
+                               $title
+                       )
+               );
+               $this->assertEquals( 1, $callableCallCounter );
+       }
+
+       public function testResetNotificationTimestamp_noItemForced() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $title = Title::newFromText( 'SomeDbKey' );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'selectRow' );
+
+               $mockCache = $this->getMockCache();
+               $mockDb->expects( $this->never() )
+                       ->method( 'set' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               // Note: This does not actually assert the job is correct
+               $callableCallCounter = 0;
+               $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
+                       $callableCallCounter++;
+                       $this->assertInternalType( 'callable', $callable );
+               };
+               $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
+
+               $this->assertTrue(
+                       $store->resetNotificationTimestamp(
+                               $user,
+                               $title,
+                               'force'
+                       )
+               );
+               $this->assertEquals( 1, $callableCallCounter );
+       }
+
+       /**
+        * @param $text
+        * @param int $ns
+        *
+        * @return PHPUnit_Framework_MockObject_MockObject|Title
+        */
+       private function getMockTitle( $text, $ns = 0 ) {
+               $title = $this->getMock( Title::class );
+               $title->expects( $this->any() )
+                       ->method( 'getText' )
+                       ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
+               $title->expects( $this->any() )
+                       ->method( 'getDbKey' )
+                       ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
+               $title->expects( $this->any() )
+                       ->method( 'getNamespace' )
+                       ->will( $this->returnValue( $ns ) );
+               return $title;
+       }
+
+       public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $oldid = 22;
+               $title = $this->getMockTitle( 'SomeTitle' );
+               $title->expects( $this->once() )
+                       ->method( 'getNextRevisionID' )
+                       ->with( $oldid )
+                       ->will( $this->returnValue( false ) );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->never() )
+                       ->method( 'selectRow' );
+
+               $mockCache = $this->getMockCache();
+               $mockDb->expects( $this->never() )
+                       ->method( 'set' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               // Note: This does not actually assert the job is correct
+               $callableCallCounter = 0;
+               $store->overrideDeferredUpdatesAddCallableUpdateCallback(
+                       function( $callable ) use ( &$callableCallCounter ) {
+                               $callableCallCounter++;
+                               $this->assertInternalType( 'callable', $callable );
+                       }
+               );
+
+               $this->assertTrue(
+                       $store->resetNotificationTimestamp(
+                               $user,
+                               $title,
+                               'force',
+                               $oldid
+                       )
+               );
+               $this->assertEquals( 1, $callableCallCounter );
+       }
+
+       public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $oldid = 22;
+               $title = $this->getMockTitle( 'SomeDbKey' );
+               $title->expects( $this->once() )
+                       ->method( 'getNextRevisionID' )
+                       ->with( $oldid )
+                       ->will( $this->returnValue( 33 ) );
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'selectRow' )
+                       ->with(
+                               'watchlist',
+                               'wl_notificationtimestamp',
+                               [
+                                       'wl_user' => 1,
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       )
+                       ->will( $this->returnValue(
+                               $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
+                       ) );
+
+               $mockCache = $this->getMockCache();
+               $mockDb->expects( $this->never() )
+                       ->method( 'set' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'delete' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $mockCache
+               );
+
+               // Note: This does not actually assert the job is correct
+               $addUpdateCallCounter = 0;
+               $store->overrideDeferredUpdatesAddCallableUpdateCallback(
+                       function( $callable ) use ( &$addUpdateCallCounter ) {
+                               $addUpdateCallCounter++;
+                               $this->assertInternalType( 'callable', $callable );
+                       }
+               );
+
+               $getTimestampCallCounter = 0;
+               $store->overrideRevisionGetTimestampFromIdCallback(
+                       function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
+                               $getTimestampCallCounter++;
+                               $this->assertEquals( $title, $titleParam );
+                               $this->assertEquals( $oldid, $oldidParam );
+                       }
+               );
+
+               $this->assertTrue(
+                       $store->resetNotificationTimestamp(
+                               $user,
+                               $title,
+                               'force',
+                               $oldid
+                       )
+               );
+               $this->assertEquals( 1, $addUpdateCallCounter );
+               $this->assertEquals( 1, $getTimestampCallCounter );
+       }
+
+       public function testUpdateNotificationTimestamp_watchersExist() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'watchlist' ],
+                               [ 'wl_user' ],
+                               [
+                                       'wl_user != 1',
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                                       'wl_notificationtimestamp IS NULL'
+                               ]
+                       )
+                       ->will(
+                               $this->returnValue( [
+                                       $this->getFakeRow( [ 'wl_user' => '2' ] ),
+                                       $this->getFakeRow( [ 'wl_user' => '3' ] )
+                               ] )
+                       );
+               $mockDb->expects( $this->once() )
+                       ->method( 'onTransactionIdle' )
+                       ->with( $this->isType( 'callable' ) )
+                       ->will( $this->returnCallback( function( $callable ) {
+                               $callable();
+                       } ) );
+               $mockDb->expects( $this->once() )
+                       ->method( 'update' )
+                       ->with(
+                               'watchlist',
+                               [ 'wl_notificationtimestamp' => null ],
+                               [
+                                       'wl_user' => [ 2, 3 ],
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                               ]
+                       );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       new HashBagOStuff( [ 'maxKeys' => 100 ] )
+               );
+
+               $this->assertEquals(
+                       [ 2, 3 ],
+                       $store->updateNotificationTimestamp(
+                               $this->getMockNonAnonUserWithId( 1 ),
+                               new TitleValue( 0, 'SomeDbKey' ),
+                               '20151212010101'
+                       )
+               );
+       }
+
+       public function testUpdateNotificationTimestamp_noWatchers() {
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'select' )
+                       ->with(
+                               [ 'watchlist' ],
+                               [ 'wl_user' ],
+                               [
+                                       'wl_user != 1',
+                                       'wl_namespace' => 0,
+                                       'wl_title' => 'SomeDbKey',
+                                       'wl_notificationtimestamp IS NULL'
+                               ]
+                       )
+                       ->will(
+                               $this->returnValue( [] )
+                       );
+               $mockDb->expects( $this->never() )
+                       ->method( 'onTransactionIdle' );
+               $mockDb->expects( $this->never() )
+                       ->method( 'update' );
+
+               $store = new WatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       new HashBagOStuff( [ 'maxKeys' => 100 ] )
+               );
+
+               $watchers = $store->updateNotificationTimestamp(
+                       $this->getMockNonAnonUserWithId( 1 ),
+                       new TitleValue( 0, 'SomeDbKey' ),
+                       '20151212010101'
+               );
+               $this->assertInternalType( 'array', $watchers );
+               $this->assertEmpty( $watchers );
+       }
+
+}
diff --git a/tests/phpunit/includes/WatchedItemUnitTest.php b/tests/phpunit/includes/WatchedItemUnitTest.php
new file mode 100644 (file)
index 0000000..bc37311
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @covers WatchedItem
+ */
+class WatchedItemUnitTest extends PHPUnit_Framework_TestCase {
+
+       public function provideUserTitleTimestamp() {
+               return [
+                       [ User::newFromId( 111 ), Title::newFromText( 'SomeTitle' ), null ],
+                       [ User::newFromId( 111 ), Title::newFromText( 'SomeTitle' ), '20150101010101' ],
+                       [ User::newFromId( 111 ), new TitleValue( 0, 'TVTitle', 'frag' ), '20150101010101' ],
+               ];
+       }
+
+       /**
+        * @return PHPUnit_Framework_MockObject_MockObject|WatchedItemStore
+        */
+       private function getMockWatchedItemStore() {
+               return $this->getMockBuilder( WatchedItemStore::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+       }
+
+       /**
+        * @dataProvider provideUserTitleTimestamp
+        */
+       public function testConstruction( $user, LinkTarget $linkTarget, $notifTimestamp ) {
+               $item = new WatchedItem( $user, $linkTarget, $notifTimestamp );
+
+               $this->assertSame( $user, $item->getUser() );
+               $this->assertSame( $linkTarget, $item->getLinkTarget() );
+               $this->assertSame( $notifTimestamp, $item->getNotificationTimestamp() );
+
+               // The below tests the internal WatchedItem::getTitle method
+               $this->assertInstanceOf( 'Title', $item->getTitle() );
+               $this->assertSame( $linkTarget->getDBkey(), $item->getTitle()->getDBkey() );
+               $this->assertSame( $linkTarget->getFragment(), $item->getTitle()->getFragment() );
+               $this->assertSame( $linkTarget->getNamespace(), $item->getTitle()->getNamespace() );
+               $this->assertSame( $linkTarget->getText(), $item->getTitle()->getText() );
+       }
+
+       /**
+        * @dataProvider provideUserTitleTimestamp
+        */
+       public function testFromUserTitle( $user, $linkTarget, $timestamp ) {
+               $store = $this->getMockWatchedItemStore();
+               $store->expects( $this->once() )
+                       ->method( 'loadWatchedItem' )
+                       ->with( $user, $linkTarget )
+                       ->will( $this->returnValue( new WatchedItem( $user, $linkTarget, $timestamp ) ) );
+               WatchedItemStore::overrideDefaultInstance( $store );
+
+               $item = WatchedItem::fromUserTitle( $user, $linkTarget, User::IGNORE_USER_RIGHTS );
+
+               $this->assertEquals( $user, $item->getUser() );
+               $this->assertEquals( $linkTarget, $item->getLinkTarget() );
+               $this->assertEquals( $timestamp, $item->getNotificationTimestamp() );
+       }
+
+       /**
+        * @dataProvider provideUserTitleTimestamp
+        */
+       public function testResetNotificationTimestamp( $user, $linkTarget, $timestamp ) {
+               $force = 'XXX';
+               $oldid = 999;
+
+               $store = $this->getMockWatchedItemStore();
+               $store->expects( $this->once() )
+                       ->method( 'resetNotificationTimestamp' )
+                       ->with( $user, $this->isInstanceOf( Title::class ), $force, $oldid )
+                       ->will( $this->returnCallback(
+                               function ( $user, Title $title, $force, $oldid ) use ( $linkTarget ) {
+                                       /** @var LinkTarget $linkTarget */
+                                       $this->assertInstanceOf( 'Title', $title );
+                                       $this->assertSame( $linkTarget->getDBkey(), $title->getDBkey() );
+                                       $this->assertSame( $linkTarget->getFragment(), $title->getFragment() );
+                                       $this->assertSame( $linkTarget->getNamespace(), $title->getNamespace() );
+                                       $this->assertSame( $linkTarget->getText(), $title->getText() );
+
+                                       return true;
+                               }
+                       ) );
+               WatchedItemStore::overrideDefaultInstance( $store );
+
+               $item = new WatchedItem( $user, $linkTarget, $timestamp );
+               $item->resetNotificationTimestamp( $force, $oldid );
+       }
+
+       public function testAddWatch() {
+               $title = Title::newFromText( 'SomeTitle' );
+               $timestamp = null;
+               $checkRights = 0;
+
+               /** @var User|PHPUnit_Framework_MockObject_MockObject $user */
+               $user = $this->getMock( User::class );
+               $user->expects( $this->once() )
+                       ->method( 'addWatch' )
+                       ->with( $title, $checkRights );
+
+               $item = new WatchedItem( $user, $title, $timestamp, $checkRights );
+               $this->assertTrue( $item->addWatch() );
+       }
+
+       public function testRemoveWatch() {
+               $title = Title::newFromText( 'SomeTitle' );
+               $timestamp = null;
+               $checkRights = 0;
+
+               /** @var User|PHPUnit_Framework_MockObject_MockObject $user */
+               $user = $this->getMock( User::class );
+               $user->expects( $this->once() )
+                       ->method( 'removeWatch' )
+                       ->with( $title, $checkRights );
+
+               $item = new WatchedItem( $user, $title, $timestamp, $checkRights );
+               $this->assertTrue( $item->removeWatch() );
+       }
+
+       public function provideBooleans() {
+               return [
+                       [ true ],
+                       [ false ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideBooleans
+        */
+       public function testIsWatched( $returnValue ) {
+               $title = Title::newFromText( 'SomeTitle' );
+               $timestamp = null;
+               $checkRights = 0;
+
+               /** @var User|PHPUnit_Framework_MockObject_MockObject $user */
+               $user = $this->getMock( User::class );
+               $user->expects( $this->once() )
+                       ->method( 'isWatched' )
+                       ->with( $title, $checkRights )
+                       ->will( $this->returnValue( $returnValue ) );
+
+               $item = new WatchedItem( $user, $title, $timestamp, $checkRights );
+               $this->assertEquals( $returnValue, $item->isWatched() );
+       }
+
+       public function testDuplicateEntries() {
+               $oldTitle = Title::newFromText( 'OldTitle' );
+               $newTitle = Title::newFromText( 'NewTitle' );
+
+               $store = $this->getMockWatchedItemStore();
+               $store->expects( $this->once() )
+                       ->method( 'duplicateAllAssociatedEntries' )
+                       ->with( $oldTitle, $newTitle );
+               WatchedItemStore::overrideDefaultInstance( $store );
+
+               WatchedItem::duplicateEntries( $oldTitle, $newTitle );
+       }
+
+       public function testBatchAddWatch() {
+               /** @var WatchedItem[] $items */
+               $items = [
+                       new WatchedItem( User::newFromId( 1 ), new TitleValue( 0, 'Title1' ), null ),
+                       new WatchedItem( User::newFromId( 3 ), Title::newFromText( 'Title2' ), '20150101010101' ),
+               ];
+
+               $userTargetCombinations = [];
+               foreach ( $items as $item ) {
+                       $userTargetCombinations[] = [ $item->getUser(), $item->getTitle()->getSubjectPage() ];
+                       $userTargetCombinations[] = [ $item->getUser(), $item->getTitle()->getTalkPage() ];
+               }
+
+               $store = $this->getMockWatchedItemStore();
+               $store->expects( $this->once() )
+                       ->method( 'addWatchBatch' )
+                       ->with( $userTargetCombinations );
+               WatchedItemStore::overrideDefaultInstance( $store );
+
+               WatchedItem::batchAddWatch( $items );
+       }
+
+}
index 62e175b..78cb7fb 100644 (file)
@@ -230,7 +230,7 @@ class RandomImageGenerator {
                        $points[] = $point['x'] . ',' . $point['y'];
                }
 
-               return join( " ", $points );
+               return implode( " ", $points );
        }
 
        /**
@@ -425,7 +425,7 @@ class RandomImageGenerator {
                        $components[] = mt_rand( 0, 255 );
                }
 
-               return 'rgb(' . join( ', ', $components ) . ')';
+               return 'rgb(' . implode( ', ', $components ) . ')';
        }
 
        /**
index 193034f..79cc666 100644 (file)
@@ -108,13 +108,13 @@ class OldChangesListTest extends MediaWikiLangTestCase {
                $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 );
 
                $this->assertContains(
-                       "<abbr class='newpage' title='(recentchanges-label-newpage)'>(newpageletter)</abbr>",
+                       '<abbr class="newpage" title="(recentchanges-label-newpage)">(newpageletter)</abbr>',
                        $line,
                        'new page flag'
                );
 
                $this->assertContains(
-                       "<abbr class='botedit' title='(recentchanges-label-bot)'>(boteditletter)</abbr>",
+                       '<abbr class="botedit" title="(recentchanges-label-bot)">(boteditletter)</abbr>',
                        $line,
                        'bot flag'
                );
index b50fe80..dfa92f1 100644 (file)
@@ -137,5 +137,5 @@ class WebPHandlerTest extends MediaWikiTestCase {
 }
 
 /* Python code to extract a header and convert to PHP format:
- * print '"%s"' % ''.join( '\\x%02X' % ord(c) for c in urllib.urlopen(url).read(36) )
+ * print '"%s"' % ''.implode( '\\x%02X' % ord(c) for c in urllib.urlopen(url).read(36) )
  */
index 998d2bb..534cf9b 100644 (file)
@@ -171,7 +171,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
                $gotWarnings = count( $warnings );
                if ( $gotWarnings !== $expectWarnings ) {
                        $this->fail( "Expected $expectWarnings warning(s), but got $gotWarnings:\n" .
-                               join( "\n", $warnings )
+                               implode( "\n", $warnings )
                        );
                }
        }
index b9cb6c1..542420a 100644 (file)
@@ -153,7 +153,7 @@ class ApiDocumentationTest extends MediaWikiTestCase {
                                foreach ( $globals as $k => $v ) {
                                        $g[] = "$k=" . var_export( $v, 1 );
                                }
-                               $k = "Module $path with " . join( ', ', $g );
+                               $k = "Module $path with " . implode( ', ', $g );
                                $ret[$k] = [ $path, $globals ];
                        }
                }
index 456787c..5a96dc3 100644 (file)
@@ -110,7 +110,7 @@ class GenerateJqueryMsgData extends Maintenance {
                                        $langKey = $languageCode . '_' . $key;
                                        $messages[$langKey] = $template;
                                        $tests[] = [
-                                               'name' => $languageCode . ' ' . $key . ' ' . join( ',', $args ),
+                                               'name' => $languageCode . ' ' . $key . ' ' . implode( ',', $args ),
                                                'key' => $langKey,
                                                'args' => $args,
                                                'result' => $result,