Merge "remove bizarre overflow:hidden from lists in sidebar on Vector"
authorTrevor Parscal <tparscal@wikimedia.org>
Mon, 13 Aug 2012 16:54:14 +0000 (16:54 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 13 Aug 2012 16:54:14 +0000 (16:54 +0000)
194 files changed:
.jshintignore
.jshintrc
RELEASE-NOTES-1.20
docs/hooks.txt
includes/AutoLoader.php
includes/Exception.php
includes/HTMLForm.php
includes/Message.php
includes/Skin.php
includes/SkinTemplate.php
includes/Title.php
includes/Uri.php
includes/User.php
includes/WikiPage.php
includes/api/ApiFormatBase.php
includes/api/ApiMain.php
includes/filebackend/FSFile.php [new file with mode: 0644]
includes/filebackend/FSFileBackend.php [new file with mode: 0644]
includes/filebackend/FileBackend.php [new file with mode: 0644]
includes/filebackend/FileBackendGroup.php [new file with mode: 0644]
includes/filebackend/FileBackendMultiWrite.php [new file with mode: 0644]
includes/filebackend/FileBackendStore.php [new file with mode: 0644]
includes/filebackend/FileOp.php [new file with mode: 0644]
includes/filebackend/FileOpBatch.php [new file with mode: 0644]
includes/filebackend/SwiftFileBackend.php [new file with mode: 0644]
includes/filebackend/TempFSFile.php [new file with mode: 0644]
includes/filebackend/filejournal/DBFileJournal.php [new file with mode: 0644]
includes/filebackend/filejournal/FileJournal.php [new file with mode: 0644]
includes/filebackend/lockmanager/DBLockManager.php [new file with mode: 0644]
includes/filebackend/lockmanager/FSLockManager.php [new file with mode: 0644]
includes/filebackend/lockmanager/LSLockManager.php [new file with mode: 0644]
includes/filebackend/lockmanager/LockManager.php [new file with mode: 0644]
includes/filebackend/lockmanager/LockManagerGroup.php [new file with mode: 0644]
includes/filebackend/lockmanager/MemcLockManager.php [new file with mode: 0644]
includes/filerepo/backend/FSFile.php [deleted file]
includes/filerepo/backend/FSFileBackend.php [deleted file]
includes/filerepo/backend/FileBackend.php [deleted file]
includes/filerepo/backend/FileBackendGroup.php [deleted file]
includes/filerepo/backend/FileBackendMultiWrite.php [deleted file]
includes/filerepo/backend/FileBackendStore.php [deleted file]
includes/filerepo/backend/FileOp.php [deleted file]
includes/filerepo/backend/FileOpBatch.php [deleted file]
includes/filerepo/backend/SwiftFileBackend.php [deleted file]
includes/filerepo/backend/TempFSFile.php [deleted file]
includes/filerepo/backend/filejournal/DBFileJournal.php [deleted file]
includes/filerepo/backend/filejournal/FileJournal.php [deleted file]
includes/filerepo/backend/lockmanager/DBLockManager.php [deleted file]
includes/filerepo/backend/lockmanager/FSLockManager.php [deleted file]
includes/filerepo/backend/lockmanager/LSLockManager.php [deleted file]
includes/filerepo/backend/lockmanager/LockManager.php [deleted file]
includes/filerepo/backend/lockmanager/LockManagerGroup.php [deleted file]
includes/filerepo/backend/lockmanager/MemcLockManager.php [deleted file]
includes/installer/DatabaseInstaller.php
includes/installer/Installer.i18n.php
includes/installer/WebInstallerPage.php
includes/json/FormatJson.php
includes/media/Bitmap.php
includes/parser/Parser.php
includes/specials/SpecialCachedPage.php
includes/specials/SpecialEmailuser.php
includes/upload/UploadBase.php
includes/upload/UploadFromChunks.php
includes/upload/UploadFromFile.php
includes/upload/UploadFromStash.php
includes/upload/UploadFromUrl.php
includes/upload/UploadStash.php
languages/classes/LanguageUz.php
languages/messages/MessagesAeb.php
languages/messages/MessagesAf.php
languages/messages/MessagesAr.php
languages/messages/MessagesArc.php
languages/messages/MessagesBa.php
languages/messages/MessagesBcl.php
languages/messages/MessagesBe_tarask.php
languages/messages/MessagesCa.php
languages/messages/MessagesCkb.php
languages/messages/MessagesCs.php
languages/messages/MessagesCy.php
languages/messages/MessagesDa.php
languages/messages/MessagesDe.php
languages/messages/MessagesDiq.php
languages/messages/MessagesDsb.php
languages/messages/MessagesEn.php
languages/messages/MessagesEo.php
languages/messages/MessagesEs.php
languages/messages/MessagesEt.php
languages/messages/MessagesFa.php
languages/messages/MessagesFi.php
languages/messages/MessagesFo.php
languages/messages/MessagesFr.php
languages/messages/MessagesFrp.php
languages/messages/MessagesGd.php
languages/messages/MessagesGl.php
languages/messages/MessagesGu.php
languages/messages/MessagesHe.php
languages/messages/MessagesHsb.php
languages/messages/MessagesHu.php
languages/messages/MessagesHy.php
languages/messages/MessagesIa.php
languages/messages/MessagesId.php
languages/messages/MessagesIs.php
languages/messages/MessagesIt.php
languages/messages/MessagesJa.php
languages/messages/MessagesJv.php
languages/messages/MessagesKa.php
languages/messages/MessagesKab.php
languages/messages/MessagesKo.php
languages/messages/MessagesLad.php
languages/messages/MessagesLb.php
languages/messages/MessagesLv.php
languages/messages/MessagesMk.php
languages/messages/MessagesMl.php
languages/messages/MessagesMs.php
languages/messages/MessagesMt.php
languages/messages/MessagesMy.php
languages/messages/MessagesNl.php
languages/messages/MessagesNn.php
languages/messages/MessagesOr.php
languages/messages/MessagesPa.php
languages/messages/MessagesPl.php
languages/messages/MessagesPms.php
languages/messages/MessagesPs.php
languages/messages/MessagesPt.php
languages/messages/MessagesQqq.php
languages/messages/MessagesRif.php
languages/messages/MessagesRo.php
languages/messages/MessagesRoa_tara.php
languages/messages/MessagesRu.php
languages/messages/MessagesRue.php
languages/messages/MessagesSa.php
languages/messages/MessagesSi.php
languages/messages/MessagesSq.php
languages/messages/MessagesSr_ec.php
languages/messages/MessagesSr_el.php
languages/messages/MessagesSu.php
languages/messages/MessagesSv.php
languages/messages/MessagesSw.php
languages/messages/MessagesTa.php
languages/messages/MessagesTe.php
languages/messages/MessagesTet.php
languages/messages/MessagesTl.php
languages/messages/MessagesTr.php
languages/messages/MessagesUg_arab.php
languages/messages/MessagesUz.php
languages/messages/MessagesVi.php
languages/messages/MessagesVot.php
languages/messages/MessagesWar.php
languages/messages/MessagesYi.php
languages/messages/MessagesZh_hans.php
languages/messages/MessagesZh_hant.php
maintenance/archives/patch-filejournal.sql
maintenance/copyFileBackend.php
maintenance/language/messages.inc
maintenance/reassignEdits.php
maintenance/rebuildFileCache.php
maintenance/rebuildImages.php
maintenance/rebuildLocalisationCache.php
maintenance/rebuildall.php
maintenance/rebuildmessages.php
maintenance/rebuildrecentchanges.php
maintenance/rebuildtextindex.php
maintenance/refreshImageMetadata.php
maintenance/removeUnusedAccounts.php
resources/jquery/jquery.js
resources/jquery/jquery.makeCollapsible.js
resources/jquery/jquery.textSelection.js
resources/mediawiki.action/mediawiki.action.edit.js
resources/mediawiki.action/mediawiki.action.history.js
resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js
resources/mediawiki.action/mediawiki.action.view.metadata.js
resources/mediawiki.language/mediawiki.language.init.js
resources/mediawiki.language/mediawiki.language.js
resources/mediawiki.page/mediawiki.page.startup.js
resources/mediawiki.page/mediawiki.page.watch.ajax.js
resources/mediawiki.special/mediawiki.special.changeemail.js
resources/mediawiki.special/mediawiki.special.javaScriptTest.js
resources/mediawiki.special/mediawiki.special.recentchanges.js
resources/mediawiki.special/mediawiki.special.upload.js
resources/mediawiki/mediawiki.Title.js
resources/mediawiki/mediawiki.Uri.js
resources/mediawiki/mediawiki.debug.js
resources/mediawiki/mediawiki.feedback.js
resources/mediawiki/mediawiki.htmlform.js
resources/mediawiki/mediawiki.jqueryMsg.js
resources/mediawiki/mediawiki.js
resources/mediawiki/mediawiki.log.js
resources/mediawiki/mediawiki.user.js
resources/mediawiki/mediawiki.util.js
skins/common/shared.css
skins/common/wikibits.js
skins/monobook/main.css
skins/vector/screen.css
skins/vector/vector.js
tests/phpunit/MediaWikiTestCase.php

index 8ba7fc3..9534f97 100644 (file)
@@ -18,6 +18,3 @@ resources/jquery.ui
 resources/mediawiki.libs/mediawiki.libs.jpegmeta.js
 tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js
 tests/jasmine/lib/jasmine-1.0.1/jasmine.js
-
-# legacy stuff
-skins/common
index 5fb1173..4cf86b8 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,7 +1,6 @@
 {
        "predef": [
                "mediaWiki",
-               "mw",
                "QUnit"
        ],
 
        "immed": true,
        "latedef": true,
        "newcap": true,
+       "noarg": true,
        "noempty": true,
+       "nonew": true,
+       "regexp": false,
        "undef": true,
+       "strict": false,
        "trailing": true,
 
        "laxbreak": true,
@@ -20,5 +23,8 @@
        "multistr": true,
 
        "browser": true,
-       "jquery": true
+       "jquery": true,
+
+       "nomen": true,
+       "onevar": false
 }
index dfd747f..c6b161f 100644 (file)
@@ -63,7 +63,7 @@ upgrade PHP if you have not done so prior to upgrading MediaWiki.
 * (bug 35685) api.php URL and other entry point URLs are now listed on
   Special:Version
 * Edit notices can now be translated.
-* (bug 35680) jQuery upgraded to 1.7.2.
+* jQuery upgraded to 1.8.
 * jQuery UI upgraded to 1.8.22.
 * (bug 35705) QUnit upgraded from v1.2.0 to v1.8.0.
 * (bug 37604) jquery.cookie upgraded to 2011 version.
@@ -119,6 +119,7 @@ upgrade PHP if you have not done so prior to upgrading MediaWiki.
 * HTMLForm mutators can now be chained (they return $this)
 * A new message, "api-error-filetype-banned-type", is available for formatting
   API upload errors due to the file extension blacklist.
+* jsMessage: Redesigned in Vector/Monobook as floating bubble with auto-hide.
 
 === Bug fixes in 1.20 ===
 * (bug 30245) Use the correct way to construct a log page title.
index eefb46c..cce152d 100644 (file)
@@ -266,7 +266,7 @@ $reason: the reason for the move (added in 1.13)
 
 'AbortNewAccount': Return false to cancel explicit account creation.
 $user: the User object about to be created (read-only, incomplete)
-&$msg: out parameter: name of error message to display on abort
+&$msg: out parameter: HTML to display on abort
 
 'ActionBeforeFormDisplay': before executing the HTMLForm object
 $name: name of the action
index e50430e..98abb96 100644 (file)
@@ -525,6 +525,50 @@ $wgAutoloadLocalClasses = array(
        'ExternalUser_MediaWiki' => 'includes/extauth/MediaWiki.php',
        'ExternalUser_vB' => 'includes/extauth/vB.php',
 
+       # includes/filebackend
+       'FileBackendGroup' => 'includes/filebackend/FileBackendGroup.php',
+       'FileBackend' => 'includes/filebackend/FileBackend.php',
+       'FileBackendStore' => 'includes/filebackend/FileBackendStore.php',
+       'FileBackendStoreShardListIterator' => 'includes/filebackend/FileBackendStore.php',
+       'FileBackendStoreShardDirIterator' => 'includes/filebackend/FileBackendStore.php',
+       'FileBackendStoreShardFileIterator' => 'includes/filebackend/FileBackendStore.php',
+       'FileBackendMultiWrite' => 'includes/filebackend/FileBackendMultiWrite.php',
+       'FileBackendStoreOpHandle' => 'includes/filebackend/FileBackendStore.php',
+       'FSFile' => 'includes/filebackend/FSFile.php',
+       'FSFileBackend' => 'includes/filebackend/FSFileBackend.php',
+       'FSFileBackendList' => 'includes/filebackend/FSFileBackend.php',
+       'FSFileBackendDirList' => 'includes/filebackend/FSFileBackend.php',
+       'FSFileBackendFileList' => 'includes/filebackend/FSFileBackend.php',
+       'FSFileOpHandle' => 'includes/filebackend/FSFileBackend.php',
+       'SwiftFileBackend' => 'includes/filebackend/SwiftFileBackend.php',
+       'SwiftFileBackendList' => 'includes/filebackend/SwiftFileBackend.php',
+       'SwiftFileBackendDirList' => 'includes/filebackend/SwiftFileBackend.php',
+       'SwiftFileBackendFileList' => 'includes/filebackend/SwiftFileBackend.php',
+       'SwiftFileOpHandle' => 'includes/filebackend/SwiftFileBackend.php',
+       'TempFSFile' => 'includes/filebackend/TempFSFile.php',
+       'FileJournal' => 'includes/filebackend/filejournal/FileJournal.php',
+       'DBFileJournal' => 'includes/filebackend/filejournal/DBFileJournal.php',
+       'NullFileJournal' => 'includes/filebackend/filejournal/FileJournal.php',
+       'LockManagerGroup' => 'includes/filebackend/lockmanager/LockManagerGroup.php',
+       'LockManager' => 'includes/filebackend/lockmanager/LockManager.php',
+       'ScopedLock' => 'includes/filebackend/lockmanager/LockManager.php',
+       'FSLockManager' => 'includes/filebackend/lockmanager/FSLockManager.php',
+       'DBLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
+       'LSLockManager' => 'includes/filebackend/lockmanager/LSLockManager.php',
+       'MemcLockManager' => 'includes/filebackend/lockmanager/MemcLockManager.php',
+       'QuorumLockManager' => 'includes/filebackend/lockmanager/LockManager.php',
+       'MySqlLockManager'=> 'includes/filebackend/lockmanager/DBLockManager.php',
+       'NullLockManager' => 'includes/filebackend/lockmanager/LockManager.php',
+       'FileOp' => 'includes/filebackend/FileOp.php',
+       'FileOpBatch' => 'includes/filebackend/FileOpBatch.php',
+       'StoreFileOp' => 'includes/filebackend/FileOp.php',
+       'CopyFileOp' => 'includes/filebackend/FileOp.php',
+       'MoveFileOp' => 'includes/filebackend/FileOp.php',
+       'DeleteFileOp' => 'includes/filebackend/FileOp.php',
+       'ConcatenateFileOp' => 'includes/filebackend/FileOp.php',
+       'CreateFileOp' => 'includes/filebackend/FileOp.php',
+       'NullFileOp' => 'includes/filebackend/FileOp.php',
+
        # includes/filerepo
        'FileRepo' => 'includes/filerepo/FileRepo.php',
        'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php',
@@ -548,50 +592,6 @@ $wgAutoloadLocalClasses = array(
        'LocalFileRestoreBatch' => 'includes/filerepo/file/LocalFile.php',
        'OldLocalFile' => 'includes/filerepo/file/OldLocalFile.php',
        'UnregisteredLocalFile' => 'includes/filerepo/file/UnregisteredLocalFile.php',
-       'FSFile' => 'includes/filerepo/backend/FSFile.php',
-       'TempFSFile' => 'includes/filerepo/backend/TempFSFile.php',
-
-       # includes/filerepo/backend
-       'FileBackendGroup' => 'includes/filerepo/backend/FileBackendGroup.php',
-       'FileBackend' => 'includes/filerepo/backend/FileBackend.php',
-       'FileBackendStore' => 'includes/filerepo/backend/FileBackendStore.php',
-       'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackendStore.php',
-       'FileBackendStoreShardDirIterator' => 'includes/filerepo/backend/FileBackendStore.php',
-       'FileBackendStoreShardFileIterator' => 'includes/filerepo/backend/FileBackendStore.php',
-       'FileBackendMultiWrite' => 'includes/filerepo/backend/FileBackendMultiWrite.php',
-       'FileBackendStoreOpHandle' => 'includes/filerepo/backend/FileBackendStore.php',
-       'FSFileBackend' => 'includes/filerepo/backend/FSFileBackend.php',
-       'FSFileBackendList' => 'includes/filerepo/backend/FSFileBackend.php',
-       'FSFileBackendDirList' => 'includes/filerepo/backend/FSFileBackend.php',
-       'FSFileBackendFileList' => 'includes/filerepo/backend/FSFileBackend.php',
-       'FSFileOpHandle' => 'includes/filerepo/backend/FSFileBackend.php',
-       'SwiftFileBackend' => 'includes/filerepo/backend/SwiftFileBackend.php',
-       'SwiftFileBackendList' => 'includes/filerepo/backend/SwiftFileBackend.php',
-       'SwiftFileBackendDirList' => 'includes/filerepo/backend/SwiftFileBackend.php',
-       'SwiftFileBackendFileList' => 'includes/filerepo/backend/SwiftFileBackend.php',
-       'SwiftFileOpHandle' => 'includes/filerepo/backend/SwiftFileBackend.php',
-       'FileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
-       'DBFileJournal' => 'includes/filerepo/backend/filejournal/DBFileJournal.php',
-       'NullFileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
-       'LockManagerGroup' => 'includes/filerepo/backend/lockmanager/LockManagerGroup.php',
-       'LockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
-       'ScopedLock' => 'includes/filerepo/backend/lockmanager/LockManager.php',
-       'FSLockManager' => 'includes/filerepo/backend/lockmanager/FSLockManager.php',
-       'DBLockManager' => 'includes/filerepo/backend/lockmanager/DBLockManager.php',
-       'LSLockManager' => 'includes/filerepo/backend/lockmanager/LSLockManager.php',
-       'MemcLockManager' => 'includes/filerepo/backend/lockmanager/MemcLockManager.php',
-       'QuorumLockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
-       'MySqlLockManager'=> 'includes/filerepo/backend/lockmanager/DBLockManager.php',
-       'NullLockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
-       'FileOp' => 'includes/filerepo/backend/FileOp.php',
-       'FileOpBatch' => 'includes/filerepo/backend/FileOpBatch.php',
-       'StoreFileOp' => 'includes/filerepo/backend/FileOp.php',
-       'CopyFileOp' => 'includes/filerepo/backend/FileOp.php',
-       'MoveFileOp' => 'includes/filerepo/backend/FileOp.php',
-       'DeleteFileOp' => 'includes/filerepo/backend/FileOp.php',
-       'ConcatenateFileOp' => 'includes/filerepo/backend/FileOp.php',
-       'CreateFileOp' => 'includes/filerepo/backend/FileOp.php',
-       'NullFileOp' => 'includes/filerepo/backend/FileOp.php',
 
        # includes/installer
        'CliInstaller' => 'includes/installer/CliInstaller.php',
index 7d4c551..d5cf543 100644 (file)
@@ -185,7 +185,7 @@ class MWException extends Exception {
 
        /**
         * Return the requested URL and point to file and line number from which the
-        * exception occured
+        * exception occurred
         *
         * @return string
         */
index ce9c377..e617f17 100644 (file)
@@ -1109,7 +1109,7 @@ abstract class HTMLFormField {
         * @return Mixed Bool true on success, or String error to display.
         */
        function validate( $value, $alldata ) {
-               if ( isset( $this->mParams['required'] ) && $value === '' ) {
+               if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value === '' ) {
                        return wfMsgExt( 'htmlform-required', 'parseinline' );
                }
 
@@ -2153,7 +2153,7 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
                        return $p;
                }
 
-               if ( isset( $this->mParams['required'] ) && $value[1] === '' ) {
+               if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value[1] === '' ) {
                        return wfMsgExt( 'htmlform-required', 'parseinline' );
                }
 
index 78b9ec2..f559d30 100644 (file)
@@ -324,6 +324,7 @@ class Message {
         * Request the message in any language that is supported.
         * As a side effect interface message status is unconditionally
         * turned off.
+        * @since 1.17
         * @param $lang Mixed: language code or Language object.
         * @return Message: $this
         */
@@ -347,6 +348,7 @@ class Message {
        /**
         * Request the message in the wiki's content language,
         * unless it is disabled for this message.
+        * @since 1.17
         * @see $wgForceUIMsgAsContentMsg
         * @return Message: $this
         */
index f4960c1..22cc0c9 100644 (file)
@@ -72,7 +72,7 @@ abstract class Skin extends ContextSource {
                }
                return $wgValidSkinNames;
        }
+
        /**
         * Fetch the skinname messages for available skins.
         * @return array of strings
@@ -1342,29 +1342,58 @@ abstract class Skin extends ContextSource {
                $ntl = '';
 
                if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
-                       $userTitle = $this->getUser()->getUserPage();
-                       $userTalkTitle = $userTitle->getTalkPage();
+                       $userTalkTitle = $this->getUser()->getTalkPage();
 
                        if ( !$userTalkTitle->equals( $out->getTitle() ) ) {
+                               $lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
+                               $nofAuthors = 0;
+                               if ( $lastSeenRev !== null ) {
+                                       $plural = true; // Default if we have a last seen revision: if unknown, use plural
+                                       $latestRev = Revision::newFromTitle ($userTalkTitle);
+                                       if ( $latestRev !== null ) {
+                                               // Singular if only 1 unseen revision, plural if several unseen revisions.\r
+                                               $plural = $latestRev->getParentId() !== $lastSeenRev->getId();\r
+                                               $nofAuthors = $userTalkTitle->countAuthorsBetween( $lastSeenRev, $latestRev, 10, 'include_new' );
+                                       }
+                               } else {
+                                       // Singular if no revision -> diff link will show latest change only in any case
+                                       $plural = false;
+                               }
+                               $plural = $plural ? 2 : 1;
+                               // 2 signifies "more than one revision". We don't know how many, and even if we did,
+                               // the number of revisions or authors is not necessarily the same as the number of
+                               // "messages".
                                $newMessagesLink = Linker::linkKnown(
                                        $userTalkTitle,
-                                       $this->msg( 'newmessageslink' )->escaped(),
+                                       $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
                                        array(),
                                        array( 'redirect' => 'no' )
                                );
 
                                $newMessagesDiffLink = Linker::linkKnown(
                                        $userTalkTitle,
-                                       $this->msg( 'newmessagesdifflink' )->escaped(),
+                                       $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
                                        array(),
-                                       array( 'diff' => 'cur' )
+                                       $lastSeenRev !== null
+                                               ? array( 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' )
+                                               : array( 'diff' => 'cur' )
                                );
 
-                               $ntl = $this->msg(
-                                       'youhavenewmessages',
-                                       $newMessagesLink,
-                                       $newMessagesDiffLink
-                               )->text();
+                               if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
+                                       $ntl = $this->msg(
+                                               'youhavenewmessagesfromusers',
+                                               $newMessagesLink,
+                                               $newMessagesDiffLink
+                                       )->numParams( $nofAuthors );
+                               } else {
+                                       // $nofAuthors === 11 signifies "11 or more" ("more than 10")
+                                       $ntl = $this->msg(
+                                               $nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
+                                               $newMessagesLink,
+                                               $newMessagesDiffLink
+                                       );
+                               }
+                               $ntl = $ntl->text();
                                # Disable Squid cache
                                $out->setSquidMaxage( 0 );
                        }
index 3f07dd6..2ddbfe9 100644 (file)
@@ -1420,6 +1420,7 @@ abstract class BaseTemplate extends QuickTemplate {
                }
                if ( isset( $this->data['nav_urls']['print'] ) && $this->data['nav_urls']['print'] ) {
                        $toolbox['print'] = $this->data['nav_urls']['print'];
+                       $toolbox['print']['id'] = 't-print';
                        $toolbox['print']['rel'] = 'alternate';
                        $toolbox['print']['msg'] = 'printableversion';
                }
index 4f7984e..642f8dd 100644 (file)
@@ -825,7 +825,7 @@ class Title {
 
        /**
         * Returns true if the title is inside the specified namespace.
-        * 
+        *
         * Please make use of this instead of comparing to getNamespace()
         * This function is much more resistant to changes we may make
         * to namespaces than code that makes direct comparisons.
@@ -1882,7 +1882,7 @@ class Title {
                                        $title_protection['pt_create_perm'] = 'protect'; // B/C
                                }
                                if( $title_protection['pt_create_perm'] == '' ||
-                                       !$user->isAllowed( $title_protection['pt_create_perm'] ) ) 
+                                       !$user->isAllowed( $title_protection['pt_create_perm'] ) )
                                {
                                        $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
                                }
@@ -4079,30 +4079,60 @@ class Title {
        }
 
        /**
-        * Get the number of authors between the given revision IDs.
+        * Get the number of authors between the given revisions or revision IDs.
         * Used for diffs and other things that really need it.
         *
-        * @param $old int|Revision Old revision or rev ID (first before range)
-        * @param $new int|Revision New revision or rev ID (first after range)
-        * @param $limit Int Maximum number of authors
-        * @return Int Number of revision authors between these revisions.
-        */
-       public function countAuthorsBetween( $old, $new, $limit ) {
+        * @param $old int|Revision Old revision or rev ID (first before range by default)
+        * @param $new int|Revision New revision or rev ID (first after range by default)
+        * @param $limit int Maximum number of authors
+        * @param $options string|array (Optional): Single option, or an array of options:
+        *     'include_old' Include $old in the range; $new is excluded.
+        *     'include_new' Include $new in the range; $old is excluded.
+        *     'include_both' Include both $old and $new in the range.
+        *     Unknown option values are ignored.
+        * @return int Number of revision authors in the range; zero if not both revisions exist
+        */
+       public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
                if ( !( $old instanceof Revision ) ) {
                        $old = Revision::newFromTitle( $this, (int)$old );
                }
                if ( !( $new instanceof Revision ) ) {
                        $new = Revision::newFromTitle( $this, (int)$new );
                }
+               // XXX: what if Revision objects are passed in, but they don't refer to this title?
+               // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
+               // in the sanity check below?
                if ( !$old || !$new ) {
                        return 0; // nothing to compare
                }
+               $old_cmp = '>';
+               $new_cmp = '<';
+               $options = (array) $options;
+               if ( in_array( 'include_old', $options ) ) {
+                       $old_cmp = '>=';
+               }
+               if ( in_array( 'include_new', $options ) ) {\r
+                       $new_cmp = '<=';\r
+               }\r
+               if ( in_array( 'include_both', $options ) ) {\r
+                       $old_cmp = '>=';\r
+                       $new_cmp = '<=';
+               }
+               // No DB query needed if $old and $new are the same or successive revisions:
+               if ( $old->getId() === $new->getId() ) {
+                       return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
+               } else if ( $old->getId() === $new->getParentId() ) {
+                       if ( $old_cmp === '>' || $new_cmp === '<' ) {
+                               return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
+                       }
+                       return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
+               }\r
                $dbr = wfGetDB( DB_SLAVE );
                $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
                        array(
                                'rev_page' => $this->getArticleID(),
-                               'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
-                               'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
+                               "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
+                               "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
                        ), __METHOD__,
                        array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
                );
index 9354068..f6cf06f 100644 (file)
@@ -253,15 +253,13 @@ class Uri {
         * @return Uri this URI object
         */
        public function extendQuery( $parameters ) {
-               if ( is_string( $parameters ) ) {
+               if ( !is_array( $parameters ) ) {
                        $parameters = wfCgiToArray( $parameters );
                }
 
                $query = $this->getQuery();
-               if ( count( $parameters ) ) {
-                       foreach( $parameters as $key => $value ) {
-                               $query[$key] = $value;
-                       }
+               foreach( $parameters as $key => $value ) {
+                       $query[$key] = $value;
                }
 
                $this->setQuery( $query );
index aef49fc..d2ebabb 100644 (file)
@@ -1789,14 +1789,20 @@ class User {
         */
        public function getNewMessageLinks() {
                $talks = array();
-               if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) )
+               if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
                        return $talks;
-
-               if( !$this->getNewtalk() )
+               } elseif( !$this->getNewtalk() ) {
                        return array();
-               $up = $this->getUserPage();
-               $utp = $up->getTalkPage();
-               return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL() ) );
+               }
+               $utp = $this->getTalkPage();
+               $dbr = wfGetDB( DB_SLAVE );
+               // Get the "last viewed rev" timestamp from the oldest message notification
+               $timestamp = $dbr->selectField( 'user_newtalk',
+                       'MIN(user_last_timestamp)',
+                       $this->isAnon() ? array( 'user_ip' => $this->getName() ) : array( 'user_id' => $this->getID() ),
+                       __METHOD__ );
+               $rev = $timestamp ? Revision::loadFromTimestamp( $dbr, $utp, $timestamp ) : null;
+               return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL(), 'rev' => $rev ) );
        }
 
        /**
@@ -1823,12 +1829,17 @@ class User {
         * Add or update the new messages flag
         * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
         * @param $id String|Int User's IP address for anonymous users, User ID otherwise
+        * @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null.
         * @return Bool True if successful, false otherwise
         */
-       protected function updateNewtalk( $field, $id ) {
+       protected function updateNewtalk( $field, $id, $curRev = null ) {
+               // Get timestamp of the talk page revision prior to the current one
+               $prevRev = $curRev ? $curRev->getPrevious() : false;
+               $ts = $prevRev ? $prevRev->getTimestamp() : null;
+               // Mark the user as having new messages since this revision
                $dbw = wfGetDB( DB_MASTER );
                $dbw->insert( 'user_newtalk',
-                       array( $field => $id ),
+                       array( $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ),
                        __METHOD__,
                        'IGNORE' );
                if ( $dbw->affectedRows() ) {
@@ -1863,8 +1874,9 @@ class User {
        /**
         * Update the 'You have new messages!' status.
         * @param $val Bool Whether the user has new messages
+        * @param $curRev Revision new, as yet unseen revision of the user talk page. Ignored if null or !$val.
         */
-       public function setNewtalk( $val ) {
+       public function setNewtalk( $val, $curRev = null ) {
                if( wfReadOnly() ) {
                        return;
                }
@@ -1882,7 +1894,7 @@ class User {
                global $wgMemc;
 
                if( $val ) {
-                       $changed = $this->updateNewtalk( $field, $id );
+                       $changed = $this->updateNewtalk( $field, $id, $curRev );
                } else {
                        $changed = $this->deleteNewtalk( $field, $id );
                }
index 764a831..9dc9c2c 100644 (file)
@@ -1788,9 +1788,9 @@ class WikiPage extends Page implements IDBAccessObject {
                                        wfDebug( __METHOD__ . ": invalid username\n" );
                                } elseif ( User::isIP( $shortTitle ) ) {
                                        // An anonymous user
-                                       $other->setNewtalk( true );
+                                       $other->setNewtalk( true, $revision );
                                } elseif ( $other->isLoggedIn() ) {
-                                       $other->setNewtalk( true );
+                                       $other->setNewtalk( true, $revision );
                                } else {
                                        wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
                                }
index 1c6a871..a8d69f5 100644 (file)
@@ -326,7 +326,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
         */
        public static function setResult( $result, $feed, $feedItems ) {
                // Store output in the Result data.
-               // This way we can check during execution if any error has occured
+               // This way we can check during execution if any error has occurred
                // Disable size checking for this because we can't continue
                // cleanly; size checking would cause more problems than it'd
                // solve
@@ -371,7 +371,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
                        }
                        $feed->outFooter();
                } else {
-                       // Error has occured, print something useful
+                       // Error has occurred, print something useful
                        ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
                }
        }
index 5e97293..420acd3 100644 (file)
@@ -393,7 +393,7 @@ class ApiMain extends ApiBase {
                        // Reset and print just the error message
                        ob_clean();
 
-                       // If the error occured during printing, do a printer->profileOut()
+                       // If the error occurred during printing, do a printer->profileOut()
                        $this->mPrinter->safeProfileOut();
                        $this->printResult( true );
                }
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php
new file mode 100644 (file)
index 0000000..e07c99d
--- /dev/null
@@ -0,0 +1,252 @@
+<?php
+/**
+ * Non-directory file on the file system.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ */
+
+/**
+ * Class representing a non-directory file on the file system
+ *
+ * @ingroup FileBackend
+ */
+class FSFile {
+       protected $path; // path to file
+
+       /**
+        * Sets up the file object
+        *
+        * @param $path string Path to temporary file on local disk
+        * @throws MWException
+        */
+       public function __construct( $path ) {
+               if ( FileBackend::isStoragePath( $path ) ) {
+                       throw new MWException( __METHOD__ . " given storage path `$path`." );
+               }
+               $this->path = $path;
+       }
+
+       /**
+        * Returns the file system path
+        *
+        * @return String
+        */
+       public function getPath() {
+               return $this->path;
+       }
+
+       /**
+        * Checks if the file exists
+        *
+        * @return bool
+        */
+       public function exists() {
+               return is_file( $this->path );
+       }
+
+       /**
+        * Get the file size in bytes
+        *
+        * @return int|bool
+        */
+       public function getSize() {
+               return filesize( $this->path );
+       }
+
+       /**
+        * Get the file's last-modified timestamp
+        *
+        * @return string|bool TS_MW timestamp or false on failure
+        */
+       public function getTimestamp() {
+               wfSuppressWarnings();
+               $timestamp = filemtime( $this->path );
+               wfRestoreWarnings();
+               if ( $timestamp !== false ) {
+                       $timestamp = wfTimestamp( TS_MW, $timestamp );
+               }
+               return $timestamp;
+       }
+
+       /**
+        * Guess the MIME type from the file contents alone
+        * 
+        * @return string 
+        */
+       public function getMimeType() {
+               return MimeMagic::singleton()->guessMimeType( $this->path, false );
+       }
+
+       /**
+        * Get an associative array containing information about
+        * a file with the given storage path.
+        *
+        * @param $ext Mixed: the file extension, or true to extract it from the filename.
+        *             Set it to false to ignore the extension.
+        *
+        * @return array
+        */
+       public function getProps( $ext = true ) {
+               wfProfileIn( __METHOD__ );
+               wfDebug( __METHOD__.": Getting file info for $this->path\n" );
+
+               $info = self::placeholderProps();
+               $info['fileExists'] = $this->exists();
+
+               if ( $info['fileExists'] ) {
+                       $magic = MimeMagic::singleton();
+
+                       # get the file extension
+                       if ( $ext === true ) {
+                               $ext = self::extensionFromPath( $this->path );
+                       }
+
+                       # mime type according to file contents
+                       $info['file-mime'] = $this->getMimeType();
+                       # logical mime type
+                       $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
+
+                       list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
+                       $info['media_type'] = $magic->getMediaType( $this->path, $info['mime'] );
+
+                       # Get size in bytes
+                       $info['size'] = $this->getSize();
+
+                       # Height, width and metadata
+                       $handler = MediaHandler::getHandler( $info['mime'] );
+                       if ( $handler ) {
+                               $tempImage = (object)array();
+                               $info['metadata'] = $handler->getMetadata( $tempImage, $this->path );
+                               $gis = $handler->getImageSize( $tempImage, $this->path, $info['metadata'] );
+                               if ( is_array( $gis ) ) {
+                                       $info = $this->extractImageSizeInfo( $gis ) + $info;
+                               }
+                       }
+                       $info['sha1'] = $this->getSha1Base36();
+
+                       wfDebug(__METHOD__.": $this->path loaded, {$info['size']} bytes, {$info['mime']}.\n");
+               } else {
+                       wfDebug(__METHOD__.": $this->path NOT FOUND!\n");
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $info;
+       }
+
+       /**
+        * Placeholder file properties to use for files that don't exist
+        *
+        * @return Array
+        */
+       public static function placeholderProps() {
+               $info = array();
+               $info['fileExists'] = false;
+               $info['mime'] = null;
+               $info['media_type'] = MEDIATYPE_UNKNOWN;
+               $info['metadata'] = '';
+               $info['sha1'] = '';
+               $info['width'] = 0;
+               $info['height'] = 0;
+               $info['bits'] = 0;
+               return $info;
+       }
+
+       /**
+        * Exract image size information
+        *
+        * @param $gis array
+        * @return Array
+        */
+       protected function extractImageSizeInfo( array $gis ) {
+               $info = array();
+               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
+               $info['width'] = $gis[0];
+               $info['height'] = $gis[1];
+               if ( isset( $gis['bits'] ) ) {
+                       $info['bits'] = $gis['bits'];
+               } else {
+                       $info['bits'] = 0;
+               }
+               return $info;
+       }
+
+       /**
+        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+        * encoding, zero padded to 31 digits.
+        *
+        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+        * fairly neatly.
+        *
+        * @return bool|string False on failure
+        */
+       public function getSha1Base36() {
+               wfProfileIn( __METHOD__ );
+
+               wfSuppressWarnings();
+               $hash = sha1_file( $this->path );
+               wfRestoreWarnings();
+               if ( $hash !== false ) {
+                       $hash = wfBaseConvert( $hash, 16, 36, 31 );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $hash;
+       }
+
+       /**
+        * Get the final file extension from a file system path
+        * 
+        * @param $path string
+        * @return string
+        */
+       public static function extensionFromPath( $path ) {
+               $i = strrpos( $path, '.' );
+               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+       }
+
+       /**
+        * Get an associative array containing information about a file in the local filesystem.
+        *
+        * @param $path String: absolute local filesystem path
+        * @param $ext Mixed: the file extension, or true to extract it from the filename.
+        *             Set it to false to ignore the extension.
+        *
+        * @return array
+        */
+       public static function getPropsFromPath( $path, $ext = true ) {
+               $fsFile = new self( $path );
+               return $fsFile->getProps( $ext );
+       }
+
+       /**
+        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+        * encoding, zero padded to 31 digits.
+        *
+        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+        * fairly neatly.
+        *
+        * @param $path string
+        *
+        * @return bool|string False on failure
+        */
+       public static function getSha1Base36FromPath( $path ) {
+               $fsFile = new self( $path );
+               return $fsFile->getSha1Base36();
+       }
+}
diff --git a/includes/filebackend/FSFileBackend.php b/includes/filebackend/FSFileBackend.php
new file mode 100644 (file)
index 0000000..c953d77
--- /dev/null
@@ -0,0 +1,976 @@
+<?php
+/**
+ * File system based backend.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Class for a file system (FS) based file backend.
+ *
+ * All "containers" each map to a directory under the backend's base directory.
+ * For backwards-compatibility, some container paths can be set to custom paths.
+ * The wiki ID will not be used in any custom paths, so this should be avoided.
+ *
+ * Having directories with thousands of files will diminish performance.
+ * Sharding can be accomplished by using FileRepo-style hash paths.
+ *
+ * Status messages should avoid mentioning the internal FS paths.
+ * PHP warnings are assumed to be logged rather than output.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class FSFileBackend extends FileBackendStore {
+       protected $basePath; // string; directory holding the container directories
+       /** @var Array Map of container names to root paths */
+       protected $containerPaths = array(); // for custom container paths
+       protected $fileMode; // integer; file permission mode
+
+       protected $hadWarningErrors = array();
+
+       /**
+        * @see FileBackendStore::__construct()
+        * Additional $config params include:
+        *   - basePath       : File system directory that holds containers.
+        *   - containerPaths : Map of container names to custom file system directories.
+        *                      This should only be used for backwards-compatibility.
+        *   - fileMode       : Octal UNIX file permissions to use on files stored.
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+
+               // Remove any possible trailing slash from directories
+               if ( isset( $config['basePath'] ) ) {
+                       $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
+               } else {
+                       $this->basePath = null; // none; containers must have explicit paths
+               }
+
+               if ( isset( $config['containerPaths'] ) ) {
+                       $this->containerPaths = (array)$config['containerPaths'];
+                       foreach ( $this->containerPaths as &$path ) {
+                               $path = rtrim( $path, '/' );  // remove trailing slash
+                       }
+               }
+
+               $this->fileMode = isset( $config['fileMode'] )
+                       ? $config['fileMode']
+                       : 0644;
+       }
+
+       /**
+        * @see FileBackendStore::resolveContainerPath()
+        * @param $container string
+        * @param $relStoragePath string
+        * @return null|string
+        */
+       protected function resolveContainerPath( $container, $relStoragePath ) {
+               // Check that container has a root directory
+               if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
+                       // Check for sane relative paths (assume the base paths are OK)
+                       if ( $this->isLegalRelPath( $relStoragePath ) ) {
+                               return $relStoragePath;
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Sanity check a relative file system path for validity
+        *
+        * @param $path string Normalized relative path
+        * @return bool
+        */
+       protected function isLegalRelPath( $path ) {
+               // Check for file names longer than 255 chars
+               if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
+                       return false;
+               }
+               if ( wfIsWindows() ) { // NTFS
+                       return !preg_match( '![:*?"<>|]!', $path );
+               } else {
+                       return true;
+               }
+       }
+
+       /**
+        * Given the short (unresolved) and full (resolved) name of
+        * a container, return the file system path of the container.
+        *
+        * @param $shortCont string
+        * @param $fullCont string
+        * @return string|null
+        */
+       protected function containerFSRoot( $shortCont, $fullCont ) {
+               if ( isset( $this->containerPaths[$shortCont] ) ) {
+                       return $this->containerPaths[$shortCont];
+               } elseif ( isset( $this->basePath ) ) {
+                       return "{$this->basePath}/{$fullCont}";
+               }
+               return null; // no container base path defined
+       }
+
+       /**
+        * Get the absolute file system path for a storage path
+        *
+        * @param $storagePath string Storage path
+        * @return string|null
+        */
+       protected function resolveToFSPath( $storagePath ) {
+               list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
+               if ( $relPath === null ) {
+                       return null; // invalid
+               }
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $storagePath );
+               $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               if ( $relPath != '' ) {
+                       $fsPath .= "/{$relPath}";
+               }
+               return $fsPath;
+       }
+
+       /**
+        * @see FileBackendStore::isPathUsableInternal()
+        * @return bool
+        */
+       public function isPathUsableInternal( $storagePath ) {
+               $fsPath = $this->resolveToFSPath( $storagePath );
+               if ( $fsPath === null ) {
+                       return false; // invalid
+               }
+               $parentDir = dirname( $fsPath );
+
+               if ( file_exists( $fsPath ) ) {
+                       $ok = is_file( $fsPath ) && is_writable( $fsPath );
+               } else {
+                       $ok = is_dir( $parentDir ) && is_writable( $parentDir );
+               }
+
+               return $ok;
+       }
+
+       /**
+        * @see FileBackendStore::doStoreInternal()
+        * @return Status
+        */
+       protected function doStoreInternal( array $params ) {
+               $status = Status::newGood();
+
+               $dest = $this->resolveToFSPath( $params['dst'] );
+               if ( $dest === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               if ( file_exists( $dest ) ) {
+                       if ( !empty( $params['overwrite'] ) ) {
+                               $ok = unlink( $dest );
+                               if ( !$ok ) {
+                                       $status->fatal( 'backend-fail-delete', $params['dst'] );
+                                       return $status;
+                               }
+                       } else {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               }
+
+               if ( !empty( $params['async'] ) ) { // deferred
+                       $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
+                               wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
+                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
+                       ) );
+                       $status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest );
+               } else { // immediate write
+                       $ok = copy( $params['src'], $dest );
+                       // In some cases (at least over NFS), copy() returns true when it fails
+                       if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
+                               if ( $ok ) { // PHP bug
+                                       unlink( $dest ); // remove broken file
+                                       trigger_error( __METHOD__ . ": copy() failed but returned true." );
+                               }
+                               $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+                               return $status;
+                       }
+                       $this->chmod( $dest );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FSFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseStore( $errors, Status $status, array $params, $cmd ) {
+               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+                       $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doCopyInternal()
+        * @return Status
+        */
+       protected function doCopyInternal( array $params ) {
+               $status = Status::newGood();
+
+               $source = $this->resolveToFSPath( $params['src'] );
+               if ( $source === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+                       return $status;
+               }
+
+               $dest = $this->resolveToFSPath( $params['dst'] );
+               if ( $dest === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               if ( file_exists( $dest ) ) {
+                       if ( !empty( $params['overwrite'] ) ) {
+                               $ok = unlink( $dest );
+                               if ( !$ok ) {
+                                       $status->fatal( 'backend-fail-delete', $params['dst'] );
+                                       return $status;
+                               }
+                       } else {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               }
+
+               if ( !empty( $params['async'] ) ) { // deferred
+                       $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
+                               wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
+                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
+                       ) );
+                       $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
+               } else { // immediate write
+                       $ok = copy( $source, $dest );
+                       // In some cases (at least over NFS), copy() returns true when it fails
+                       if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
+                               if ( $ok ) { // PHP bug
+                                       unlink( $dest ); // remove broken file
+                                       trigger_error( __METHOD__ . ": copy() failed but returned true." );
+                               }
+                               $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+                               return $status;
+                       }
+                       $this->chmod( $dest );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FSFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseCopy( $errors, Status $status, array $params, $cmd ) {
+               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doMoveInternal()
+        * @return Status
+        */
+       protected function doMoveInternal( array $params ) {
+               $status = Status::newGood();
+
+               $source = $this->resolveToFSPath( $params['src'] );
+               if ( $source === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+                       return $status;
+               }
+
+               $dest = $this->resolveToFSPath( $params['dst'] );
+               if ( $dest === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               if ( file_exists( $dest ) ) {
+                       if ( !empty( $params['overwrite'] ) ) {
+                               // Windows does not support moving over existing files
+                               if ( wfIsWindows() ) {
+                                       $ok = unlink( $dest );
+                                       if ( !$ok ) {
+                                               $status->fatal( 'backend-fail-delete', $params['dst'] );
+                                               return $status;
+                                       }
+                               }
+                       } else {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               }
+
+               if ( !empty( $params['async'] ) ) { // deferred
+                       $cmd = implode( ' ', array( wfIsWindows() ? 'MOVE' : 'mv',
+                               wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
+                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
+                       ) );
+                       $status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
+               } else { // immediate write
+                       $ok = rename( $source, $dest );
+                       clearstatcache(); // file no longer at source
+                       if ( !$ok ) {
+                               $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+                               return $status;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FSFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseMove( $errors, Status $status, array $params, $cmd ) {
+               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doDeleteInternal()
+        * @return Status
+        */
+       protected function doDeleteInternal( array $params ) {
+               $status = Status::newGood();
+
+               $source = $this->resolveToFSPath( $params['src'] );
+               if ( $source === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+                       return $status;
+               }
+
+               if ( !is_file( $source ) ) {
+                       if ( empty( $params['ignoreMissingSource'] ) ) {
+                               $status->fatal( 'backend-fail-delete', $params['src'] );
+                       }
+                       return $status; // do nothing; either OK or bad status
+               }
+
+               if ( !empty( $params['async'] ) ) { // deferred
+                       $cmd = implode( ' ', array( wfIsWindows() ? 'DEL' : 'unlink',
+                               wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
+                       ) );
+                       $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd );
+               } else { // immediate write
+                       $ok = unlink( $source );
+                       if ( !$ok ) {
+                               $status->fatal( 'backend-fail-delete', $params['src'] );
+                               return $status;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FSFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseDelete( $errors, Status $status, array $params, $cmd ) {
+               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+                       $status->fatal( 'backend-fail-delete', $params['src'] );
+                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doCreateInternal()
+        * @return Status
+        */
+       protected function doCreateInternal( array $params ) {
+               $status = Status::newGood();
+
+               $dest = $this->resolveToFSPath( $params['dst'] );
+               if ( $dest === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               if ( file_exists( $dest ) ) {
+                       if ( !empty( $params['overwrite'] ) ) {
+                               $ok = unlink( $dest );
+                               if ( !$ok ) {
+                                       $status->fatal( 'backend-fail-delete', $params['dst'] );
+                                       return $status;
+                               }
+                       } else {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               }
+
+               if ( !empty( $params['async'] ) ) { // deferred
+                       $tempFile = TempFSFile::factory( 'create_', 'tmp' );
+                       if ( !$tempFile ) {
+                               $status->fatal( 'backend-fail-create', $params['dst'] );
+                               return $status;
+                       }
+                       $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
+                       if ( $bytes === false ) {
+                               $status->fatal( 'backend-fail-create', $params['dst'] );
+                               return $status;
+                       }
+                       $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
+                               wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
+                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
+                       ) );
+                       $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
+                       $tempFile->bind( $status->value );
+               } else { // immediate write
+                       $bytes = file_put_contents( $dest, $params['content'] );
+                       if ( $bytes === false ) {
+                               $status->fatal( 'backend-fail-create', $params['dst'] );
+                               return $status;
+                       }
+                       $this->chmod( $dest );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FSFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
+               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
+                       $status->fatal( 'backend-fail-create', $params['dst'] );
+                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doPrepareInternal()
+        * @return Status
+        */
+       protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
+               $status = Status::newGood();
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+               $existed = is_dir( $dir ); // already there?
+               if ( !wfMkdirParents( $dir ) ) { // make directory and its parents
+                       $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
+               } elseif ( !is_writable( $dir ) ) {
+                       $status->fatal( 'directoryreadonlyerror', $params['dir'] );
+               } elseif ( !is_readable( $dir ) ) {
+                       $status->fatal( 'directorynotreadableerror', $params['dir'] );
+               }
+               if ( is_dir( $dir ) && !$existed ) {
+                       // Respect any 'noAccess' or 'noListing' flags...
+                       $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
+               }
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doSecureInternal()
+        * @return Status
+        */
+       protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
+               $status = Status::newGood();
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+               // Seed new directories with a blank index.html, to prevent crawling...
+               if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
+                       $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
+                       if ( $bytes === false ) {
+                               $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
+                               return $status;
+                       }
+               }
+               // Add a .htaccess file to the root of the container...
+               if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
+                       $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
+                       if ( $bytes === false ) {
+                               $storeDir = "mwstore://{$this->name}/{$shortCont}";
+                               $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
+                               return $status;
+                       }
+               }
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doPublishInternal()
+        * @return Status
+        */
+       protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
+               $status = Status::newGood();
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+               // Unseed new directories with a blank index.html, to allow crawling...
+               if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
+                       $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
+                       if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
+                               $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
+                               return $status;
+                       }
+               }
+               // Remove the .htaccess file from the root of the container...
+               if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
+                       $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
+                       if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
+                               $storeDir = "mwstore://{$this->name}/{$shortCont}";
+                               $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
+                               return $status;
+                       }
+               }
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doCleanInternal()
+        * @return Status
+        */
+       protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
+               $status = Status::newGood();
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+               wfSuppressWarnings();
+               if ( is_dir( $dir ) ) {
+                       rmdir( $dir ); // remove directory if empty
+               }
+               wfRestoreWarnings();
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doFileExists()
+        * @return array|bool|null
+        */
+       protected function doGetFileStat( array $params ) {
+               $source = $this->resolveToFSPath( $params['src'] );
+               if ( $source === null ) {
+                       return false; // invalid storage path
+               }
+
+               $this->trapWarnings(); // don't trust 'false' if there were errors
+               $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
+               $hadError = $this->untrapWarnings();
+
+               if ( $stat ) {
+                       return array(
+                               'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
+                               'size'  => $stat['size']
+                       );
+               } elseif ( !$hadError ) {
+                       return false; // file does not exist
+               } else {
+                       return null; // failure
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doClearCache()
+        */
+       protected function doClearCache( array $paths = null ) {
+               clearstatcache(); // clear the PHP file stat cache
+       }
+
+       /**
+        * @see FileBackendStore::doDirectoryExists()
+        * @return bool|null
+        */
+       protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+
+               $this->trapWarnings(); // don't trust 'false' if there were errors
+               $exists = is_dir( $dir );
+               $hadError = $this->untrapWarnings();
+
+               return $hadError ? null : $exists;
+       }
+
+       /**
+        * @see FileBackendStore::getDirectoryListInternal()
+        * @return Array|null
+        */
+       public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+               $exists = is_dir( $dir );
+               if ( !$exists ) {
+                       wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
+                       return array(); // nothing under this dir
+               } elseif ( !is_readable( $dir ) ) {
+                       wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
+                       return null; // bad permissions?
+               }
+               return new FSFileBackendDirList( $dir, $params );
+       }
+
+       /**
+        * @see FileBackendStore::getFileListInternal()
+        * @return array|FSFileBackendFileList|null
+        */
+       public function getFileListInternal( $fullCont, $dirRel, array $params ) {
+               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+               $exists = is_dir( $dir );
+               if ( !$exists ) {
+                       wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
+                       return array(); // nothing under this dir
+               } elseif ( !is_readable( $dir ) ) {
+                       wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
+                       return null; // bad permissions?
+               }
+               return new FSFileBackendFileList( $dir, $params );
+       }
+
+       /**
+        * @see FileBackendStore::getLocalReference()
+        * @return FSFile|null
+        */
+       public function getLocalReference( array $params ) {
+               $source = $this->resolveToFSPath( $params['src'] );
+               if ( $source === null ) {
+                       return null;
+               }
+               return new FSFile( $source );
+       }
+
+       /**
+        * @see FileBackendStore::getLocalCopy()
+        * @return null|TempFSFile
+        */
+       public function getLocalCopy( array $params ) {
+               $source = $this->resolveToFSPath( $params['src'] );
+               if ( $source === null ) {
+                       return null;
+               }
+
+               // Create a new temporary file with the same extension...
+               $ext = FileBackend::extensionFromPath( $params['src'] );
+               $tmpFile = TempFSFile::factory( wfBaseName( $source ) . '_', $ext );
+               if ( !$tmpFile ) {
+                       return null;
+               }
+               $tmpPath = $tmpFile->getPath();
+
+               // Copy the source file over the temp file
+               $ok = copy( $source, $tmpPath );
+               if ( !$ok ) {
+                       return null;
+               }
+
+               $this->chmod( $tmpPath );
+
+               return $tmpFile;
+       }
+
+       /**
+        * @see FileBackendStore::directoriesAreVirtual()
+        * @return bool
+        */
+       protected function directoriesAreVirtual() {
+               return false;
+       }
+
+       /**
+        * @see FileBackendStore::doExecuteOpHandlesInternal()
+        * @return Array List of corresponding Status objects
+        */
+       protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
+               $statuses = array();
+
+               $pipes = array();
+               foreach ( $fileOpHandles as $index => $fileOpHandle ) {
+                       $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
+               }
+
+               $errs = array();
+               foreach ( $pipes as $index => $pipe ) {
+                       // Result will be empty on success in *NIX. On Windows,
+                       // it may be something like "        1 file(s) [copied|moved].".
+                       $errs[$index] = stream_get_contents( $pipe );
+                       fclose( $pipe );
+               }
+
+               foreach ( $fileOpHandles as $index => $fileOpHandle ) {
+                       $status = Status::newGood();
+                       $function = '_getResponse' . $fileOpHandle->call;
+                       $this->$function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
+                       $statuses[$index] = $status;
+                       if ( $status->isOK() && $fileOpHandle->chmodPath ) {
+                               $this->chmod( $fileOpHandle->chmodPath );
+                       }
+               }
+
+               clearstatcache(); // files changed
+               return $statuses;
+       }
+
+       /**
+        * Chmod a file, suppressing the warnings
+        *
+        * @param $path string Absolute file system path
+        * @return bool Success
+        */
+       protected function chmod( $path ) {
+               wfSuppressWarnings();
+               $ok = chmod( $path, $this->fileMode );
+               wfRestoreWarnings();
+
+               return $ok;
+       }
+
+       /**
+        * Return the text of an index.html file to hide directory listings
+        *
+        * @return string
+        */
+       protected function indexHtmlPrivate() {
+               return '';
+       }
+
+       /**
+        * Return the text of a .htaccess file to make a directory private
+        *
+        * @return string
+        */
+       protected function htaccessPrivate() {
+               return "Deny from all\n";
+       }
+
+       /**
+        * Clean up directory separators for the given OS
+        *
+        * @param $path string FS path
+        * @return string
+        */
+       protected function cleanPathSlashes( $path ) {
+               return wfIsWindows() ? strtr( $path, '/', '\\' ) : $path;
+       }
+
+       /**
+        * Listen for E_WARNING errors and track whether any happen
+        *
+        * @return bool
+        */
+       protected function trapWarnings() {
+               $this->hadWarningErrors[] = false; // push to stack
+               set_error_handler( array( $this, 'handleWarning' ), E_WARNING );
+               return false; // invoke normal PHP error handler
+       }
+
+       /**
+        * Stop listening for E_WARNING errors and return true if any happened
+        *
+        * @return bool
+        */
+       protected function untrapWarnings() {
+               restore_error_handler(); // restore previous handler
+               return array_pop( $this->hadWarningErrors ); // pop from stack
+       }
+
+       /**
+        * @return bool
+        */
+       private function handleWarning() {
+               $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
+               return true; // suppress from PHP handler
+       }
+}
+
+/**
+ * @see FileBackendStoreOpHandle
+ */
+class FSFileOpHandle extends FileBackendStoreOpHandle {
+       public $cmd; // string; shell command
+       public $chmodPath; // string; file to chmod
+
+       /**
+        * @param $backend
+        * @param $params array
+        * @param $call
+        * @param $cmd
+        * @param $chmodPath null
+        */
+       public function __construct( $backend, array $params, $call, $cmd, $chmodPath = null ) {
+               $this->backend = $backend;
+               $this->params = $params;
+               $this->call = $call;
+               $this->cmd = $cmd;
+               $this->chmodPath = $chmodPath;
+       }
+}
+
+/**
+ * Wrapper around RecursiveDirectoryIterator/DirectoryIterator that
+ * catches exception or does any custom behavoir that we may want.
+ * Do not use this class from places outside FSFileBackend.
+ *
+ * @ingroup FileBackend
+ */
+abstract class FSFileBackendList implements Iterator {
+       /** @var Iterator */
+       protected $iter;
+       protected $suffixStart; // integer
+       protected $pos = 0; // integer
+       /** @var Array */
+       protected $params = array();
+
+       /**
+        * @param $dir string file system directory
+        * @param $params array
+        */
+       public function __construct( $dir, array $params ) {
+               $dir = realpath( $dir ); // normalize
+               $this->suffixStart = strlen( $dir ) + 1; // size of "path/to/dir/"
+               $this->params = $params;
+
+               try {
+                       $this->iter = $this->initIterator( $dir );
+               } catch ( UnexpectedValueException $e ) {
+                       $this->iter = null; // bad permissions? deleted?
+               }
+       }
+
+       /**
+        * Return an appropriate iterator object to wrap
+        *
+        * @param $dir string file system directory
+        * @return Iterator
+        */
+       protected function initIterator( $dir ) {
+               if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
+                       # Get an iterator that will get direct sub-nodes
+                       return new DirectoryIterator( $dir );
+               } else { // recursive
+                       # Get an iterator that will return leaf nodes (non-directories)
+                       # RecursiveDirectoryIterator extends FilesystemIterator.
+                       # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
+                       $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
+                       return new RecursiveIteratorIterator(
+                               new RecursiveDirectoryIterator( $dir, $flags ),
+                               RecursiveIteratorIterator::CHILD_FIRST // include dirs
+                       );
+               }
+       }
+
+       /**
+        * @see Iterator::key()
+        * @return integer
+        */
+       public function key() {
+               return $this->pos;
+       }
+
+       /**
+        * @see Iterator::current()
+        * @return string|bool String or false
+        */
+       public function current() {
+               return $this->getRelPath( $this->iter->current()->getPathname() );
+       }
+
+       /**
+        * @see Iterator::next()
+        * @return void
+        */
+       public function next() {
+               try {
+                       $this->iter->next();
+                       $this->filterViaNext();
+               } catch ( UnexpectedValueException $e ) {
+                       $this->iter = null;
+               }
+               ++$this->pos;
+       }
+
+       /**
+        * @see Iterator::rewind()
+        * @return void
+        */
+       public function rewind() {
+               $this->pos = 0;
+               try {
+                       $this->iter->rewind();
+                       $this->filterViaNext();
+               } catch ( UnexpectedValueException $e ) {
+                       $this->iter = null;
+               }
+       }
+
+       /**
+        * @see Iterator::valid()
+        * @return bool
+        */
+       public function valid() {
+               return $this->iter && $this->iter->valid();
+       }
+
+       /**
+        * Filter out items by advancing to the next ones
+        */
+       protected function filterViaNext() {}
+
+       /**
+        * Return only the relative path and normalize slashes to FileBackend-style.
+        * Uses the "real path" since the suffix is based upon that.
+        *
+        * @param $path string
+        * @return string
+        */
+       protected function getRelPath( $path ) {
+               return strtr( substr( realpath( $path ), $this->suffixStart ), '\\', '/' );
+       }
+}
+
+class FSFileBackendDirList extends FSFileBackendList {
+       protected function filterViaNext() {
+               while ( $this->iter->valid() ) {
+                       if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
+                               $this->iter->next(); // skip non-directories and dot files
+                       } else {
+                               break;
+                       }
+               }
+       }
+}
+
+class FSFileBackendFileList extends FSFileBackendList {
+       protected function filterViaNext() {
+               while ( $this->iter->valid() ) {
+                       if ( !$this->iter->current()->isFile() ) {
+                               $this->iter->next(); // skip non-files and dot files
+                       } else {
+                               break;
+                       }
+               }
+       }
+}
diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php
new file mode 100644 (file)
index 0000000..4b707fc
--- /dev/null
@@ -0,0 +1,1112 @@
+<?php
+/**
+ * @defgroup FileBackend File backend
+ * @ingroup  FileRepo
+ *
+ * File backend is used to interact with file storage systems,
+ * such as the local file system, NFS, or cloud storage systems.
+ */
+
+/**
+ * Base class for all file backends.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Base class for all file backend classes (including multi-write backends).
+ *
+ * This class defines the methods as abstract that subclasses must implement.
+ * Outside callers can assume that all backends will have these functions.
+ *
+ * All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
+ * The "<path>" portion is a relative path that uses UNIX file system (FS)
+ * notation, though any particular backend may not actually be using a local
+ * filesystem.
+ * Therefore, the relative paths are only virtual.
+ *
+ * Backend contents are stored under wiki-specific container names by default.
+ * For legacy reasons, this has no effect for the FS backend class, and per-wiki
+ * segregation must be done by setting the container paths appropriately.
+ *
+ * FS-based backends are somewhat more restrictive due to the existence of real
+ * directory files; a regular file cannot have the same name as a directory. Other
+ * backends with virtual directories may not have this limitation. Callers should
+ * store files in such a way that no files and directories are under the same path.
+ *
+ * Methods should avoid throwing exceptions at all costs.
+ * As a corollary, external dependencies should be kept to a minimum.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+abstract class FileBackend {
+       protected $name; // string; unique backend name
+       protected $wikiId; // string; unique wiki name
+       protected $readOnly; // string; read-only explanation message
+       protected $parallelize; // string; when to do operations in parallel
+       protected $concurrency; // integer; how many operations can be done in parallel
+
+       /** @var LockManager */
+       protected $lockManager;
+       /** @var FileJournal */
+       protected $fileJournal;
+
+       /**
+        * Create a new backend instance from configuration.
+        * This should only be called from within FileBackendGroup.
+        *
+        * $config includes:
+        *   - name        : The unique name of this backend.
+        *                   This should consist of alphanumberic, '-', and '_' characters.
+        *                   This name should not be changed after use.
+        *   - wikiId      : Prefix to container names that is unique to this wiki.
+        *                   It should only consist of alphanumberic, '-', and '_' characters.
+        *   - lockManager : Registered name of a file lock manager to use.
+        *   - fileJournal : File journal configuration; see FileJournal::factory().
+        *                   Journals simply log changes to files stored in the backend.
+        *   - readOnly    : Write operations are disallowed if this is a non-empty string.
+        *                   It should be an explanation for the backend being read-only.
+        *   - parallelize : When to do file operations in parallel (when possible).
+        *                   Allowed values are "implicit", "explicit" and "off".
+        *   - concurrency : How many file operations can be done in parallel.
+        *
+        * @param $config Array
+        * @throws MWException
+        */
+       public function __construct( array $config ) {
+               $this->name = $config['name'];
+               if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
+                       throw new MWException( "Backend name `{$this->name}` is invalid." );
+               }
+               $this->wikiId = isset( $config['wikiId'] )
+                       ? $config['wikiId']
+                       : wfWikiID(); // e.g. "my_wiki-en_"
+               $this->lockManager = ( $config['lockManager'] instanceof LockManager )
+                       ? $config['lockManager']
+                       : LockManagerGroup::singleton()->get( $config['lockManager'] );
+               $this->fileJournal = isset( $config['fileJournal'] )
+                       ? ( ( $config['fileJournal'] instanceof FileJournal )
+                               ? $config['fileJournal']
+                               : FileJournal::factory( $config['fileJournal'], $this->name ) )
+                       : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
+               $this->readOnly = isset( $config['readOnly'] )
+                       ? (string)$config['readOnly']
+                       : '';
+               $this->parallelize = isset( $config['parallelize'] )
+                       ? (string)$config['parallelize']
+                       : 'off';
+               $this->concurrency = isset( $config['concurrency'] )
+                       ? (int)$config['concurrency']
+                       : 50;
+       }
+
+       /**
+        * Get the unique backend name.
+        * We may have multiple different backends of the same type.
+        * For example, we can have two Swift backends using different proxies.
+        *
+        * @return string
+        */
+       final public function getName() {
+               return $this->name;
+       }
+
+       /**
+        * Check if this backend is read-only
+        *
+        * @return bool
+        */
+       final public function isReadOnly() {
+               return ( $this->readOnly != '' );
+       }
+
+       /**
+        * Get an explanatory message if this backend is read-only
+        *
+        * @return string|bool Returns false if the backend is not read-only
+        */
+       final public function getReadOnlyReason() {
+               return ( $this->readOnly != '' ) ? $this->readOnly : false;
+       }
+
+       /**
+        * This is the main entry point into the backend for write operations.
+        * Callers supply an ordered list of operations to perform as a transaction.
+        * Files will be locked, the stat cache cleared, and then the operations attempted.
+        * If any serious errors occur, all attempted operations will be rolled back.
+        *
+        * $ops is an array of arrays. The outer array holds a list of operations.
+        * Each inner array is a set of key value pairs that specify an operation.
+        *
+        * Supported operations and their parameters. The supported actions are:
+        *  - create
+        *  - store
+        *  - copy
+        *  - move
+        *  - delete
+        *  - null
+        *
+        * a) Create a new file in storage with the contents of a string
+        * @code
+        *     array(
+        *         'op'                  => 'create',
+        *         'dst'                 => <storage path>,
+        *         'content'             => <string of new file contents>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>
+        *     );
+        * @endcode
+        *
+        * b) Copy a file system file into storage
+        * @code
+        *     array(
+        *         'op'                  => 'store',
+        *         'src'                 => <file system path>,
+        *         'dst'                 => <storage path>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>
+        *     )
+        * @endcode
+        *
+        * c) Copy a file within storage
+        * @code
+        *     array(
+        *         'op'                  => 'copy',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>
+        *     )
+        * @endcode
+        *
+        * d) Move a file within storage
+        * @code
+        *     array(
+        *         'op'                  => 'move',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>,
+        *         'overwrite'           => <boolean>,
+        *         'overwriteSame'       => <boolean>
+        *     )
+        * @endcode
+        *
+        * e) Delete a file within storage
+        * @code
+        *     array(
+        *         'op'                  => 'delete',
+        *         'src'                 => <storage path>,
+        *         'ignoreMissingSource' => <boolean>
+        *     )
+        * @endcode
+        *
+        * f) Do nothing (no-op)
+        * @code
+        *     array(
+        *         'op'                  => 'null',
+        *     )
+        * @endcode
+        *
+        * Boolean flags for operations (operation-specific):
+        *   - ignoreMissingSource : The operation will simply succeed and do
+        *                           nothing if the source file does not exist.
+        *   - overwrite           : Any destination file will be overwritten.
+        *   - overwriteSame       : An error will not be given if a file already
+        *                           exists at the destination that has the same
+        *                           contents as the new contents to be written there.
+        *
+        * $opts is an associative of boolean flags, including:
+        *   - force               : Operation precondition errors no longer trigger an abort.
+        *                           Any remaining operations are still attempted. Unexpected
+        *                           failures may still cause remaning operations to be aborted.
+        *   - nonLocking          : No locks are acquired for the operations.
+        *                           This can increase performance for non-critical writes.
+        *                           This has no effect unless the 'force' flag is set.
+        *   - allowStale          : Don't require the latest available data.
+        *                           This can increase performance for non-critical writes.
+        *                           This has no effect unless the 'force' flag is set.
+        *   - nonJournaled        : Don't log this operation batch in the file journal.
+        *                           This limits the ability of recovery scripts.
+        *   - parallelize         : Try to do operations in parallel when possible.
+        *   - bypassReadOnly      : Allow writes in read-only mode (since 1.20).
+        *
+        * @remarks Remarks on locking:
+        * File system paths given to operations should refer to files that are
+        * already locked or otherwise safe from modification from other processes.
+        * Normally these files will be new temp files, which should be adequate.
+        *
+        * @par Return value:
+        *
+        * This returns a Status, which contains all warnings and fatals that occurred
+        * during the operation. The 'failCount', 'successCount', and 'success' members
+        * will reflect each operation attempted.
+        *
+        * The status will be "OK" unless:
+        *   - a) unexpected operation errors occurred (network partitions, disk full...)
+        *   - b) significant operation errors occurred and 'force' was not set
+        *
+        * @param $ops Array List of operations to execute in order
+        * @param $opts Array Batch operation options
+        * @return Status
+        */
+       final public function doOperations( array $ops, array $opts = array() ) {
+               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               if ( empty( $opts['force'] ) ) { // sanity
+                       unset( $opts['nonLocking'] );
+                       unset( $opts['allowStale'] );
+               }
+               $opts['concurrency'] = 1; // off
+               if ( $this->parallelize === 'implicit' ) {
+                       if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
+                               $opts['concurrency'] = $this->concurrency;
+                       }
+               } elseif ( $this->parallelize === 'explicit' ) {
+                       if ( !empty( $opts['parallelize'] ) ) {
+                               $opts['concurrency'] = $this->concurrency;
+                       }
+               }
+               return $this->doOperationsInternal( $ops, $opts );
+       }
+
+       /**
+        * @see FileBackend::doOperations()
+        */
+       abstract protected function doOperationsInternal( array $ops, array $opts );
+
+       /**
+        * Same as doOperations() except it takes a single operation.
+        * If you are doing a batch of operations that should either
+        * all succeed or all fail, then use that function instead.
+        *
+        * @see FileBackend::doOperations()
+        *
+        * @param $op Array Operation
+        * @param $opts Array Operation options
+        * @return Status
+        */
+       final public function doOperation( array $op, array $opts = array() ) {
+               return $this->doOperations( array( $op ), $opts );
+       }
+
+       /**
+        * Performs a single create operation.
+        * This sets $params['op'] to 'create' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param $params Array Operation parameters
+        * @param $opts Array Operation options
+        * @return Status
+        */
+       final public function create( array $params, array $opts = array() ) {
+               return $this->doOperation( array( 'op' => 'create' ) + $params, $opts );
+       }
+
+       /**
+        * Performs a single store operation.
+        * This sets $params['op'] to 'store' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param $params Array Operation parameters
+        * @param $opts Array Operation options
+        * @return Status
+        */
+       final public function store( array $params, array $opts = array() ) {
+               return $this->doOperation( array( 'op' => 'store' ) + $params, $opts );
+       }
+
+       /**
+        * Performs a single copy operation.
+        * This sets $params['op'] to 'copy' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param $params Array Operation parameters
+        * @param $opts Array Operation options
+        * @return Status
+        */
+       final public function copy( array $params, array $opts = array() ) {
+               return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts );
+       }
+
+       /**
+        * Performs a single move operation.
+        * This sets $params['op'] to 'move' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param $params Array Operation parameters
+        * @param $opts Array Operation options
+        * @return Status
+        */
+       final public function move( array $params, array $opts = array() ) {
+               return $this->doOperation( array( 'op' => 'move' ) + $params, $opts );
+       }
+
+       /**
+        * Performs a single delete operation.
+        * This sets $params['op'] to 'delete' and passes it to doOperation().
+        *
+        * @see FileBackend::doOperation()
+        *
+        * @param $params Array Operation parameters
+        * @param $opts Array Operation options
+        * @return Status
+        */
+       final public function delete( array $params, array $opts = array() ) {
+               return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts );
+       }
+
+       /**
+        * Perform a set of independent file operations on some files.
+        *
+        * This does no locking, nor journaling, and possibly no stat calls.
+        * Any destination files that already exist will be overwritten.
+        * This should *only* be used on non-original files, like cache files.
+        *
+        * Supported operations and their parameters:
+        *  - create
+        *  - store
+        *  - copy
+        *  - move
+        *  - delete
+        *  - null
+        *
+        * a) Create a new file in storage with the contents of a string
+        * @code
+        *     array(
+        *         'op'                  => 'create',
+        *         'dst'                 => <storage path>,
+        *         'content'             => <string of new file contents>
+        *     )
+        * @endcode
+        * b) Copy a file system file into storage
+        * @code
+        *     array(
+        *         'op'                  => 'store',
+        *         'src'                 => <file system path>,
+        *         'dst'                 => <storage path>
+        *     )
+        * @endcode
+        * c) Copy a file within storage
+        * @code
+        *     array(
+        *         'op'                  => 'copy',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>
+        *     )
+        * @endcode
+        * d) Move a file within storage
+        * @code
+        *     array(
+        *         'op'                  => 'move',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>
+        *     )
+        * @endcode
+        * e) Delete a file within storage
+        * @code
+        *     array(
+        *         'op'                  => 'delete',
+        *         'src'                 => <storage path>,
+        *         'ignoreMissingSource' => <boolean>
+        *     )
+        * @endcode
+        * f) Do nothing (no-op)
+        * @code
+        *     array(
+        *         'op'                  => 'null',
+        *     )
+        * @endcode
+        *
+        * @par Boolean flags for operations (operation-specific):
+        *   - ignoreMissingSource : The operation will simply succeed and do
+        *                           nothing if the source file does not exist.
+        *
+        * $opts is an associative of boolean flags, including:
+        *   - bypassReadOnly      : Allow writes in read-only mode (since 1.20)
+        *
+        * @par Return value:
+        * This returns a Status, which contains all warnings and fatals that occurred
+        * during the operation. The 'failCount', 'successCount', and 'success' members
+        * will reflect each operation attempted for the given files. The status will be
+        * considered "OK" as long as no fatal errors occurred.
+        *
+        * @param $ops Array Set of operations to execute
+        * @param $opts Array Batch operation options
+        * @return Status
+        * @since 1.20
+        */
+       final public function doQuickOperations( array $ops, array $opts = array() ) {
+               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               foreach ( $ops as &$op ) {
+                       $op['overwrite'] = true; // avoids RTTs in key/value stores
+               }
+               return $this->doQuickOperationsInternal( $ops );
+       }
+
+       /**
+        * @see FileBackend::doQuickOperations()
+        * @since 1.20
+        */
+       abstract protected function doQuickOperationsInternal( array $ops );
+
+       /**
+        * Same as doQuickOperations() except it takes a single operation.
+        * If you are doing a batch of operations, then use that function instead.
+        *
+        * @see FileBackend::doQuickOperations()
+        *
+        * @param $op Array Operation
+        * @return Status
+        * @since 1.20
+        */
+       final public function doQuickOperation( array $op ) {
+               return $this->doQuickOperations( array( $op ) );
+       }
+
+       /**
+        * Performs a single quick create operation.
+        * This sets $params['op'] to 'create' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param $params Array Operation parameters
+        * @return Status
+        * @since 1.20
+        */
+       final public function quickCreate( array $params ) {
+               return $this->doQuickOperation( array( 'op' => 'create' ) + $params );
+       }
+
+       /**
+        * Performs a single quick store operation.
+        * This sets $params['op'] to 'store' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param $params Array Operation parameters
+        * @return Status
+        * @since 1.20
+        */
+       final public function quickStore( array $params ) {
+               return $this->doQuickOperation( array( 'op' => 'store' ) + $params );
+       }
+
+       /**
+        * Performs a single quick copy operation.
+        * This sets $params['op'] to 'copy' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param $params Array Operation parameters
+        * @return Status
+        * @since 1.20
+        */
+       final public function quickCopy( array $params ) {
+               return $this->doQuickOperation( array( 'op' => 'copy' ) + $params );
+       }
+
+       /**
+        * Performs a single quick move operation.
+        * This sets $params['op'] to 'move' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param $params Array Operation parameters
+        * @return Status
+        * @since 1.20
+        */
+       final public function quickMove( array $params ) {
+               return $this->doQuickOperation( array( 'op' => 'move' ) + $params );
+       }
+
+       /**
+        * Performs a single quick delete operation.
+        * This sets $params['op'] to 'delete' and passes it to doQuickOperation().
+        *
+        * @see FileBackend::doQuickOperation()
+        *
+        * @param $params Array Operation parameters
+        * @return Status
+        * @since 1.20
+        */
+       final public function quickDelete( array $params ) {
+               return $this->doQuickOperation( array( 'op' => 'delete' ) + $params );
+       }
+
+       /**
+        * Concatenate a list of storage files into a single file system file.
+        * The target path should refer to a file that is already locked or
+        * otherwise safe from modification from other processes. Normally,
+        * the file will be a new temp file, which should be adequate.
+        *
+        * @param $params Array Operation parameters
+        * $params include:
+        *   - srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
+        *   - dst  : file system path to 0-byte temp file
+        * @return Status
+        */
+       abstract public function concatenate( array $params );
+
+       /**
+        * Prepare a storage directory for usage.
+        * This will create any required containers and parent directories.
+        * Backends using key/value stores only need to create the container.
+        *
+        * The 'noAccess' and 'noListing' parameters works the same as in secure(),
+        * except they are only applied *if* the directory/container had to be created.
+        * These flags should always be set for directories that have private files.
+        *
+        * @param $params Array
+        * $params include:
+        *   - dir            : storage directory
+        *   - noAccess       : try to deny file access (since 1.20)
+        *   - noListing      : try to deny file listing (since 1.20)
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return Status
+        */
+       final public function prepare( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               return $this->doPrepare( $params );
+       }
+
+       /**
+        * @see FileBackend::prepare()
+        */
+       abstract protected function doPrepare( array $params );
+
+       /**
+        * Take measures to block web access to a storage directory and
+        * the container it belongs to. FS backends might add .htaccess
+        * files whereas key/value store backends might revoke container
+        * access to the storage user representing end-users in web requests.
+        * This is not guaranteed to actually do anything.
+        *
+        * @param $params Array
+        * $params include:
+        *   - dir            : storage directory
+        *   - noAccess       : try to deny file access
+        *   - noListing      : try to deny file listing
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return Status
+        */
+       final public function secure( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               return $this->doSecure( $params );
+       }
+
+       /**
+        * @see FileBackend::secure()
+        */
+       abstract protected function doSecure( array $params );
+
+       /**
+        * Remove measures to block web access to a storage directory and
+        * the container it belongs to. FS backends might remove .htaccess
+        * files whereas key/value store backends might grant container
+        * access to the storage user representing end-users in web requests.
+        * This essentially can undo the result of secure() calls.
+        *
+        * @param $params Array
+        * $params include:
+        *   - dir            : storage directory
+        *   - access         : try to allow file access
+        *   - listing        : try to allow file listing
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return Status
+        * @since 1.20
+        */
+       final public function publish( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               return $this->doPublish( $params );
+       }
+
+       /**
+        * @see FileBackend::publish()
+        */
+       abstract protected function doPublish( array $params );
+
+       /**
+        * Delete a storage directory if it is empty.
+        * Backends using key/value stores may do nothing unless the directory
+        * is that of an empty container, in which case it should be deleted.
+        *
+        * @param $params Array
+        * $params include:
+        *   - dir            : storage directory
+        *   - recursive      : recursively delete empty subdirectories first (since 1.20)
+        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
+        * @return Status
+        */
+       final public function clean( array $params ) {
+               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
+                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               return $this->doClean( $params );
+       }
+
+       /**
+        * @see FileBackend::clean()
+        */
+       abstract protected function doClean( array $params );
+
+       /**
+        * Check if a file exists at a storage path in the backend.
+        * This returns false if only a directory exists at the path.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return bool|null Returns null on failure
+        */
+       abstract public function fileExists( array $params );
+
+       /**
+        * Get the last-modified timestamp of the file at a storage path.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return string|bool TS_MW timestamp or false on failure
+        */
+       abstract public function getFileTimestamp( array $params );
+
+       /**
+        * Get the contents of a file at a storage path in the backend.
+        * This should be avoided for potentially large files.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return string|bool Returns false on failure
+        */
+       abstract public function getFileContents( array $params );
+
+       /**
+        * Get the size (bytes) of a file at a storage path in the backend.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return integer|bool Returns false on failure
+        */
+       abstract public function getFileSize( array $params );
+
+       /**
+        * Get quick information about a file at a storage path in the backend.
+        * If the file does not exist, then this returns false.
+        * Otherwise, the result is an associative array that includes:
+        *   - mtime  : the last-modified timestamp (TS_MW)
+        *   - size   : the file size (bytes)
+        * Additional values may be included for internal use only.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return Array|bool|null Returns null on failure
+        */
+       abstract public function getFileStat( array $params );
+
+       /**
+        * Get a SHA-1 hash of the file at a storage path in the backend.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return string|bool Hash string or false on failure
+        */
+       abstract public function getFileSha1Base36( array $params );
+
+       /**
+        * Get the properties of the file at a storage path in the backend.
+        * Returns FSFile::placeholderProps() on failure.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return Array
+        */
+       abstract public function getFileProps( array $params );
+
+       /**
+        * Stream the file at a storage path in the backend.
+        * If the file does not exists, a 404 error will be given.
+        * Appropriate HTTP headers (Status, Content-Type, Content-Length)
+        * must be sent if streaming began, while none should be sent otherwise.
+        * Implementations should flush the output buffer before sending data.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src     : source storage path
+        *   - headers : additional HTTP headers to send on success
+        *   - latest  : use the latest available data
+        * @return Status
+        */
+       abstract public function streamFile( array $params );
+
+       /**
+        * Returns a file system file, identical to the file at a storage path.
+        * The file returned is either:
+        *   - a) A local copy of the file at a storage path in the backend.
+        *        The temporary copy will have the same extension as the source.
+        *   - b) An original of the file at a storage path in the backend.
+        * Temporary files may be purged when the file object falls out of scope.
+        *
+        * Write operations should *never* be done on this file as some backends
+        * may do internal tracking or may be instances of FileBackendMultiWrite.
+        * In that later case, there are copies of the file that must stay in sync.
+        * Additionally, further calls to this function may return the same file.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return FSFile|null Returns null on failure
+        */
+       abstract public function getLocalReference( array $params );
+
+       /**
+        * Get a local copy on disk of the file at a storage path in the backend.
+        * The temporary copy will have the same file extension as the source.
+        * Temporary files may be purged when the file object falls out of scope.
+        *
+        * @param $params Array
+        * $params include:
+        *   - src    : source storage path
+        *   - latest : use the latest available data
+        * @return TempFSFile|null Returns null on failure
+        */
+       abstract public function getLocalCopy( array $params );
+
+       /**
+        * Check if a directory exists at a given storage path.
+        * Backends using key/value stores will check if the path is a
+        * virtual directory, meaning there are files under the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * @param $params array
+        * $params include:
+        *   - dir : storage directory
+        * @return bool|null Returns null on failure
+        * @since 1.20
+        */
+       abstract public function directoryExists( array $params );
+
+       /**
+        * Get an iterator to list *all* directories under a storage directory.
+        * If the directory is of the form "mwstore://backend/container",
+        * then all directories in the container should be listed.
+        * If the directory is of form "mwstore://backend/container/dir",
+        * then all directories directly under that directory should be listed.
+        * Results should be storage directories relative to the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * @param $params array
+        * $params include:
+        *   - dir     : storage directory
+        *   - topOnly : only return direct child dirs of the directory
+        * @return Traversable|Array|null Returns null on failure
+        * @since 1.20
+        */
+       abstract public function getDirectoryList( array $params );
+
+       /**
+        * Same as FileBackend::getDirectoryList() except only lists
+        * directories that are immediately under the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * @param $params array
+        * $params include:
+        *   - dir : storage directory
+        * @return Traversable|Array|null Returns null on failure
+        * @since 1.20
+        */
+       final public function getTopDirectoryList( array $params ) {
+               return $this->getDirectoryList( array( 'topOnly' => true ) + $params );
+       }
+
+       /**
+        * Get an iterator to list *all* stored files under a storage directory.
+        * If the directory is of the form "mwstore://backend/container",
+        * then all files in the container should be listed.
+        * If the directory is of form "mwstore://backend/container/dir",
+        * then all files under that directory should be listed.
+        * Results should be storage paths relative to the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * @param $params array
+        * $params include:
+        *   - dir     : storage directory
+        *   - topOnly : only return direct child files of the directory (since 1.20)
+        * @return Traversable|Array|null Returns null on failure
+        */
+       abstract public function getFileList( array $params );
+
+       /**
+        * Same as FileBackend::getFileList() except only lists
+        * files that are immediately under the given directory.
+        *
+        * Storage backends with eventual consistency might return stale data.
+        *
+        * @param $params array
+        * $params include:
+        *   - dir : storage directory
+        * @return Traversable|Array|null Returns null on failure
+        * @since 1.20
+        */
+       final public function getTopFileList( array $params ) {
+               return $this->getFileList( array( 'topOnly' => true ) + $params );
+       }
+
+       /**
+        * Invalidate any in-process file existence and property cache.
+        * If $paths is given, then only the cache for those files will be cleared.
+        *
+        * @param $paths Array Storage paths (optional)
+        * @return void
+        */
+       public function clearCache( array $paths = null ) {}
+
+       /**
+        * Lock the files at the given storage paths in the backend.
+        * This will either lock all the files or none (on failure).
+        *
+        * Callers should consider using getScopedFileLocks() instead.
+        *
+        * @param $paths Array Storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @return Status
+        */
+       final public function lockFiles( array $paths, $type ) {
+               return $this->lockManager->lock( $paths, $type );
+       }
+
+       /**
+        * Unlock the files at the given storage paths in the backend.
+        *
+        * @param $paths Array Storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @return Status
+        */
+       final public function unlockFiles( array $paths, $type ) {
+               return $this->lockManager->unlock( $paths, $type );
+       }
+
+       /**
+        * Lock the files at the given storage paths in the backend.
+        * This will either lock all the files or none (on failure).
+        * On failure, the status object will be updated with errors.
+        *
+        * Once the return value goes out scope, the locks will be released and
+        * the status updated. Unlock fatals will not change the status "OK" value.
+        *
+        * @param $paths Array Storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @param $status Status Status to update on lock/unlock
+        * @return ScopedLock|null Returns null on failure
+        */
+       final public function getScopedFileLocks( array $paths, $type, Status $status ) {
+               return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
+       }
+
+       /**
+        * Get an array of scoped locks needed for a batch of file operations.
+        *
+        * Normally, FileBackend::doOperations() handles locking, unless
+        * the 'nonLocking' param is passed in. This function is useful if you
+        * want the files to be locked for a broader scope than just when the
+        * files are changing. For example, if you need to update DB metadata,
+        * you may want to keep the files locked until finished.
+        *
+        * @see FileBackend::doOperations()
+        *
+        * @param $ops Array List of file operations to FileBackend::doOperations()
+        * @param $status Status Status to update on lock/unlock
+        * @return Array List of ScopedFileLocks or null values
+        * @since 1.20
+        */
+       abstract public function getScopedLocksForOps( array $ops, Status $status );
+
+       /**
+        * Get the root storage path of this backend.
+        * All container paths are "subdirectories" of this path.
+        *
+        * @return string Storage path
+        * @since 1.20
+        */
+       final public function getRootStoragePath() {
+               return "mwstore://{$this->name}";
+       }
+
+       /**
+        * Get the file journal object for this backend
+        *
+        * @return FileJournal
+        */
+       final public function getJournal() {
+               return $this->fileJournal;
+       }
+
+       /**
+        * Check if a given path is a "mwstore://" path.
+        * This does not do any further validation or any existence checks.
+        *
+        * @param $path string
+        * @return bool
+        */
+       final public static function isStoragePath( $path ) {
+               return ( strpos( $path, 'mwstore://' ) === 0 );
+       }
+
+       /**
+        * Split a storage path into a backend name, a container name,
+        * and a relative file path. The relative path may be the empty string.
+        * This does not do any path normalization or traversal checks.
+        *
+        * @param $storagePath string
+        * @return Array (backend, container, rel object) or (null, null, null)
+        */
+       final public static function splitStoragePath( $storagePath ) {
+               if ( self::isStoragePath( $storagePath ) ) {
+                       // Remove the "mwstore://" prefix and split the path
+                       $parts = explode( '/', substr( $storagePath, 10 ), 3 );
+                       if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
+                               if ( count( $parts ) == 3 ) {
+                                       return $parts; // e.g. "backend/container/path"
+                               } else {
+                                       return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
+                               }
+                       }
+               }
+               return array( null, null, null );
+       }
+
+       /**
+        * Normalize a storage path by cleaning up directory separators.
+        * Returns null if the path is not of the format of a valid storage path.
+        *
+        * @param $storagePath string
+        * @return string|null
+        */
+       final public static function normalizeStoragePath( $storagePath ) {
+               list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
+               if ( $relPath !== null ) { // must be for this backend
+                       $relPath = self::normalizeContainerPath( $relPath );
+                       if ( $relPath !== null ) {
+                               return ( $relPath != '' )
+                                       ? "mwstore://{$backend}/{$container}/{$relPath}"
+                                       : "mwstore://{$backend}/{$container}";
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Get the parent storage directory of a storage path.
+        * This returns a path like "mwstore://backend/container",
+        * "mwstore://backend/container/...", or null if there is no parent.
+        *
+        * @param $storagePath string
+        * @return string|null
+        */
+       final public static function parentStoragePath( $storagePath ) {
+               $storagePath = dirname( $storagePath );
+               list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath );
+               return ( $rel === null ) ? null : $storagePath;
+       }
+
+       /**
+        * Get the final extension from a storage or FS path
+        *
+        * @param $path string
+        * @return string
+        */
+       final public static function extensionFromPath( $path ) {
+               $i = strrpos( $path, '.' );
+               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+       }
+
+       /**
+        * Check if a relative path has no directory traversals
+        *
+        * @param $path string
+        * @return bool
+        * @since 1.20
+        */
+       final public static function isPathTraversalFree( $path ) {
+               return ( self::normalizeContainerPath( $path ) !== null );
+       }
+
+       /**
+        * Validate and normalize a relative storage path.
+        * Null is returned if the path involves directory traversal.
+        * Traversal is insecure for FS backends and broken for others.
+        *
+        * This uses the same traversal protection as Title::secureAndSplit().
+        *
+        * @param $path string Storage path relative to a container
+        * @return string|null
+        */
+       final protected static function normalizeContainerPath( $path ) {
+               // Normalize directory separators
+               $path = strtr( $path, '\\', '/' );
+               // Collapse any consecutive directory separators
+               $path = preg_replace( '![/]{2,}!', '/', $path );
+               // Remove any leading directory separator
+               $path = ltrim( $path, '/' );
+               // Use the same traversal protection as Title::secureAndSplit()
+               if ( strpos( $path, '.' ) !== false ) {
+                       if (
+                               $path === '.' ||
+                               $path === '..' ||
+                               strpos( $path, './' ) === 0 ||
+                               strpos( $path, '../' ) === 0 ||
+                               strpos( $path, '/./' ) !== false ||
+                               strpos( $path, '/../' ) !== false
+                       ) {
+                               return null;
+                       }
+               }
+               return $path;
+       }
+}
diff --git a/includes/filebackend/FileBackendGroup.php b/includes/filebackend/FileBackendGroup.php
new file mode 100644 (file)
index 0000000..8bbc96d
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+/**
+ * File backend registration handling.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle file backend registration
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class FileBackendGroup {
+       /**
+        * @var FileBackendGroup
+        */
+       protected static $instance = null;
+
+       /** @var Array (name => ('class' => string, 'config' => array, 'instance' => object)) */
+       protected $backends = array();
+
+       protected function __construct() {}
+
+       /**
+        * @return FileBackendGroup
+        */
+       public static function singleton() {
+               if ( self::$instance == null ) {
+                       self::$instance = new self();
+                       self::$instance->initFromGlobals();
+               }
+               return self::$instance;
+       }
+
+       /**
+        * Destroy the singleton instance
+        *
+        * @return void
+        */
+       public static function destroySingleton() {
+               self::$instance = null;
+       }
+
+       /**
+        * Register file backends from the global variables
+        *
+        * @return void
+        */
+       protected function initFromGlobals() {
+               global $wgLocalFileRepo, $wgForeignFileRepos, $wgFileBackends;
+
+               // Register explicitly defined backends
+               $this->register( $wgFileBackends );
+
+               $autoBackends = array();
+               // Automatically create b/c backends for file repos...
+               $repos = array_merge( $wgForeignFileRepos, array( $wgLocalFileRepo ) );
+               foreach ( $repos as $info ) {
+                       $backendName = $info['backend'];
+                       if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
+                               continue; // already defined (or set to the object for some reason)
+                       }
+                       $repoName = $info['name'];
+                       // Local vars that used to be FSRepo members...
+                       $directory = $info['directory'];
+                       $deletedDir = isset( $info['deletedDir'] )
+                               ? $info['deletedDir']
+                               : false; // deletion disabled
+                       $thumbDir = isset( $info['thumbDir'] )
+                               ? $info['thumbDir']
+                               : "{$directory}/thumb";
+                       $fileMode = isset( $info['fileMode'] )
+                               ? $info['fileMode']
+                               : 0644;
+                       // Get the FS backend configuration
+                       $autoBackends[] = array(
+                               'name'           => $backendName,
+                               'class'          => 'FSFileBackend',
+                               'lockManager'    => 'fsLockManager',
+                               'containerPaths' => array(
+                                       "{$repoName}-public"  => "{$directory}",
+                                       "{$repoName}-thumb"   => $thumbDir,
+                                       "{$repoName}-deleted" => $deletedDir,
+                                       "{$repoName}-temp"    => "{$directory}/temp"
+                               ),
+                               'fileMode'       => $fileMode,
+                       );
+               }
+
+               // Register implicitly defined backends
+               $this->register( $autoBackends );
+       }
+
+       /**
+        * Register an array of file backend configurations
+        *
+        * @param $configs Array
+        * @return void
+        * @throws MWException
+        */
+       protected function register( array $configs ) {
+               foreach ( $configs as $config ) {
+                       if ( !isset( $config['name'] ) ) {
+                               throw new MWException( "Cannot register a backend with no name." );
+                       }
+                       $name = $config['name'];
+                       if ( !isset( $config['class'] ) ) {
+                               throw new MWException( "Cannot register backend `{$name}` with no class." );
+                       }
+                       $class = $config['class'];
+
+                       unset( $config['class'] ); // backend won't need this
+                       $this->backends[$name] = array(
+                               'class'    => $class,
+                               'config'   => $config,
+                               'instance' => null
+                       );
+               }
+       }
+
+       /**
+        * Get the backend object with a given name
+        *
+        * @param $name string
+        * @return FileBackend
+        * @throws MWException
+        */
+       public function get( $name ) {
+               if ( !isset( $this->backends[$name] ) ) {
+                       throw new MWException( "No backend defined with the name `$name`." );
+               }
+               // Lazy-load the actual backend instance
+               if ( !isset( $this->backends[$name]['instance'] ) ) {
+                       $class = $this->backends[$name]['class'];
+                       $config = $this->backends[$name]['config'];
+                       $this->backends[$name]['instance'] = new $class( $config );
+               }
+               return $this->backends[$name]['instance'];
+       }
+
+       /**
+        * Get the config array for a backend object with a given name
+        *
+        * @param $name string
+        * @return Array
+        * @throws MWException
+        */
+       public function config( $name ) {
+               if ( !isset( $this->backends[$name] ) ) {
+                       throw new MWException( "No backend defined with the name `$name`." );
+               }
+               $class = $this->backends[$name]['class'];
+               return array( 'class' => $class ) + $this->backends[$name]['config'];
+       }
+
+       /**
+        * Get an appropriate backend object from a storage path
+        *
+        * @param $storagePath string
+        * @return FileBackend|null Backend or null on failure
+        */
+       public function backendFromPath( $storagePath ) {
+               list( $backend, $c, $p ) = FileBackend::splitStoragePath( $storagePath );
+               if ( $backend !== null && isset( $this->backends[$backend] ) ) {
+                       return $this->get( $backend );
+               }
+               return null;
+       }
+}
diff --git a/includes/filebackend/FileBackendMultiWrite.php b/includes/filebackend/FileBackendMultiWrite.php
new file mode 100644 (file)
index 0000000..e9136d5
--- /dev/null
@@ -0,0 +1,568 @@
+<?php
+/**
+ * Proxy backend that mirrors writes to several internal backends.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Proxy backend that mirrors writes to several internal backends.
+ *
+ * This class defines a multi-write backend. Multiple backends can be
+ * registered to this proxy backend and it will act as a single backend.
+ * Use this when all access to those backends is through this proxy backend.
+ * At least one of the backends must be declared the "master" backend.
+ *
+ * Only use this class when transitioning from one storage system to another.
+ *
+ * Read operations are only done on the 'master' backend for consistency.
+ * Write operations are performed on all backends, in the order defined.
+ * If an operation fails on one backend it will be rolled back from the others.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class FileBackendMultiWrite extends FileBackend {
+       /** @var Array Prioritized list of FileBackendStore objects */
+       protected $backends = array(); // array of (backend index => backends)
+       protected $masterIndex = -1; // integer; index of master backend
+       protected $syncChecks = 0; // integer bitfield
+
+       /* Possible internal backend consistency checks */
+       const CHECK_SIZE = 1;
+       const CHECK_TIME = 2;
+       const CHECK_SHA1 = 4;
+
+       /**
+        * Construct a proxy backend that consists of several internal backends.
+        * Locking, journaling, and read-only checks are handled by the proxy backend.
+        *
+        * Additional $config params include:
+        *   - backends   : Array of backend config and multi-backend settings.
+        *                  Each value is the config used in the constructor of a
+        *                  FileBackendStore class, but with these additional settings:
+        *                    - class         : The name of the backend class
+        *                    - isMultiMaster : This must be set for one backend.
+        *                    - template:     : If given a backend name, this will use
+        *                                      the config of that backend as a template.
+        *                                      Values specified here take precedence.
+        *   - syncChecks : Integer bitfield of internal backend sync checks to perform.
+        *                  Possible bits include the FileBackendMultiWrite::CHECK_* constants.
+        *                  There are constants for SIZE, TIME, and SHA1.
+        *                  The checks are done before allowing any file operations.
+        * @param $config Array
+        * @throws MWException
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+               $namesUsed = array();
+               // Construct backends here rather than via registration
+               // to keep these backends hidden from outside the proxy.
+               foreach ( $config['backends'] as $index => $config ) {
+                       if ( isset( $config['template'] ) ) {
+                               // Config is just a modified version of a registered backend's.
+                               // This should only be used when that config is used only by this backend.
+                               $config = $config + FileBackendGroup::singleton()->config( $config['template'] );
+                       }
+                       $name = $config['name'];
+                       if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
+                               throw new MWException( "Two or more backends defined with the name $name." );
+                       }
+                       $namesUsed[$name] = 1;
+                       // Alter certain sub-backend settings for sanity
+                       unset( $config['readOnly'] ); // use proxy backend setting
+                       unset( $config['fileJournal'] ); // use proxy backend journal
+                       $config['wikiId'] = $this->wikiId; // use the proxy backend wiki ID
+                       $config['lockManager'] = 'nullLockManager'; // lock under proxy backend
+                       if ( !empty( $config['isMultiMaster'] ) ) {
+                               if ( $this->masterIndex >= 0 ) {
+                                       throw new MWException( 'More than one master backend defined.' );
+                               }
+                               $this->masterIndex = $index; // this is the "master"
+                               $config['fileJournal'] = $this->fileJournal; // log under proxy backend
+                       }
+                       // Create sub-backend object
+                       if ( !isset( $config['class'] ) ) {
+                               throw new MWException( 'No class given for a backend config.' );
+                       }
+                       $class = $config['class'];
+                       $this->backends[$index] = new $class( $config );
+               }
+               if ( $this->masterIndex < 0 ) { // need backends and must have a master
+                       throw new MWException( 'No master backend defined.' );
+               }
+               $this->syncChecks = isset( $config['syncChecks'] )
+                       ? $config['syncChecks']
+                       : self::CHECK_SIZE;
+       }
+
+       /**
+        * @see FileBackend::doOperationsInternal()
+        * @return Status
+        */
+       final protected function doOperationsInternal( array $ops, array $opts ) {
+               $status = Status::newGood();
+
+               $mbe = $this->backends[$this->masterIndex]; // convenience
+
+               // Get the paths to lock from the master backend
+               $realOps = $this->substOpBatchPaths( $ops, $mbe );
+               $paths = $mbe->getPathsToLockForOpsInternal( $mbe->getOperationsInternal( $realOps ) );
+               // Get the paths under the proxy backend's name
+               $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
+               $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
+               // Try to lock those files for the scope of this function...
+               if ( empty( $opts['nonLocking'] ) ) {
+                       // Try to lock those files for the scope of this function...
+                       $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
+                       $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
+                       if ( !$status->isOK() ) {
+                               return $status; // abort
+                       }
+               }
+               // Clear any cache entries (after locks acquired)
+               $this->clearCache();
+               // Do a consistency check to see if the backends agree
+               $status->merge( $this->consistencyCheck( $this->fileStoragePathsForOps( $ops ) ) );
+               if ( !$status->isOK() ) {
+                       return $status; // abort
+               }
+               // Actually attempt the operation batch on the master backend...
+               $masterStatus = $mbe->doOperations( $realOps, $opts );
+               $status->merge( $masterStatus );
+               // Propagate the operations to the clone backends...
+               foreach ( $this->backends as $index => $backend ) {
+                       if ( $index !== $this->masterIndex ) { // not done already
+                               $realOps = $this->substOpBatchPaths( $ops, $backend );
+                               $status->merge( $backend->doOperations( $realOps, $opts ) );
+                       }
+               }
+               // Make 'success', 'successCount', and 'failCount' fields reflect
+               // the overall operation, rather than all the batches for each backend.
+               // Do this by only using success values from the master backend's batch.
+               $status->success = $masterStatus->success;
+               $status->successCount = $masterStatus->successCount;
+               $status->failCount = $masterStatus->failCount;
+
+               return $status;
+       }
+
+       /**
+        * Check that a set of files are consistent across all internal backends
+        *
+        * @param $paths Array
+        * @return Status
+        */
+       public function consistencyCheck( array $paths ) {
+               $status = Status::newGood();
+               if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
+                       return $status; // skip checks
+               }
+
+               $mBackend = $this->backends[$this->masterIndex];
+               foreach ( array_unique( $paths ) as $path ) {
+                       $params = array( 'src' => $path, 'latest' => true );
+                       $mParams = $this->substOpPaths( $params, $mBackend );
+                       // Stat the file on the 'master' backend
+                       $mStat = $mBackend->getFileStat( $mParams );
+                       if ( $this->syncChecks & self::CHECK_SHA1 ) {
+                               $mSha1 = $mBackend->getFileSha1( $mParams );
+                       } else {
+                               $mSha1 = false;
+                       }
+                       $mUsable = $mBackend->isPathUsableInternal( $mParams['src'] );
+                       // Check of all clone backends agree with the master...
+                       foreach ( $this->backends as $index => $cBackend ) {
+                               if ( $index === $this->masterIndex ) {
+                                       continue; // master
+                               }
+                               $cParams = $this->substOpPaths( $params, $cBackend );
+                               $cStat = $cBackend->getFileStat( $cParams );
+                               if ( $mStat ) { // file is in master
+                                       if ( !$cStat ) { // file should exist
+                                               $status->fatal( 'backend-fail-synced', $path );
+                                               continue;
+                                       }
+                                       if ( $this->syncChecks & self::CHECK_SIZE ) {
+                                               if ( $cStat['size'] != $mStat['size'] ) { // wrong size
+                                                       $status->fatal( 'backend-fail-synced', $path );
+                                                       continue;
+                                               }
+                                       }
+                                       if ( $this->syncChecks & self::CHECK_TIME ) {
+                                               $mTs = wfTimestamp( TS_UNIX, $mStat['mtime'] );
+                                               $cTs = wfTimestamp( TS_UNIX, $cStat['mtime'] );
+                                               if ( abs( $mTs - $cTs ) > 30 ) { // outdated file somewhere
+                                                       $status->fatal( 'backend-fail-synced', $path );
+                                                       continue;
+                                               }
+                                       }
+                                       if ( $this->syncChecks & self::CHECK_SHA1 ) {
+                                               if ( $cBackend->getFileSha1( $cParams ) !== $mSha1 ) { // wrong SHA1
+                                                       $status->fatal( 'backend-fail-synced', $path );
+                                                       continue;
+                                               }
+                                       }
+                               } else { // file is not in master
+                                       if ( $cStat ) { // file should not exist
+                                               $status->fatal( 'backend-fail-synced', $path );
+                                       }
+                               }
+                               if ( $mUsable !== $cBackend->isPathUsableInternal( $cParams['src'] ) ) {
+                                       $status->fatal( 'backend-fail-synced', $path );
+                               }
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * Get a list of file storage paths to read or write for a list of operations
+        *
+        * @param $ops Array Same format as doOperations()
+        * @return Array List of storage paths to files (does not include directories)
+        */
+       protected function fileStoragePathsForOps( array $ops ) {
+               $paths = array();
+               foreach ( $ops as $op ) {
+                       if ( isset( $op['src'] ) ) {
+                               $paths[] = $op['src'];
+                       }
+                       if ( isset( $op['srcs'] ) ) {
+                               $paths = array_merge( $paths, $op['srcs'] );
+                       }
+                       if ( isset( $op['dst'] ) ) {
+                               $paths[] = $op['dst'];
+                       }
+               }
+               return array_unique( $paths );
+       }
+
+       /**
+        * Substitute the backend name in storage path parameters
+        * for a set of operations with that of a given internal backend.
+        *
+        * @param $ops Array List of file operation arrays
+        * @param $backend FileBackendStore
+        * @return Array
+        */
+       protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
+               $newOps = array(); // operations
+               foreach ( $ops as $op ) {
+                       $newOp = $op; // operation
+                       foreach ( array( 'src', 'srcs', 'dst', 'dir' ) as $par ) {
+                               if ( isset( $newOp[$par] ) ) { // string or array
+                                       $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
+                               }
+                       }
+                       $newOps[] = $newOp;
+               }
+               return $newOps;
+       }
+
+       /**
+        * Same as substOpBatchPaths() but for a single operation
+        *
+        * @param $ops array File operation array
+        * @param $backend FileBackendStore
+        * @return Array
+        */
+       protected function substOpPaths( array $ops, FileBackendStore $backend ) {
+               $newOps = $this->substOpBatchPaths( array( $ops ), $backend );
+               return $newOps[0];
+       }
+
+       /**
+        * Substitute the backend of storage paths with an internal backend's name
+        *
+        * @param $paths Array|string List of paths or single string path
+        * @param $backend FileBackendStore
+        * @return Array|string
+        */
+       protected function substPaths( $paths, FileBackendStore $backend ) {
+               return preg_replace(
+                       '!^mwstore://' . preg_quote( $this->name ) . '/!',
+                       StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
+                       $paths // string or array
+               );
+       }
+
+       /**
+        * Substitute the backend of internal storage paths with the proxy backend's name
+        *
+        * @param $paths Array|string List of paths or single string path
+        * @return Array|string
+        */
+       protected function unsubstPaths( $paths ) {
+               return preg_replace(
+                       '!^mwstore://([^/]+)!',
+                       StringUtils::escapeRegexReplacement( "mwstore://{$this->name}" ),
+                       $paths // string or array
+               );
+       }
+
+       /**
+        * @see FileBackend::doQuickOperationsInternal()
+        * @return Status
+        */
+       protected function doQuickOperationsInternal( array $ops ) {
+               $status = Status::newGood();
+               // Do the operations on the master backend; setting Status fields...
+               $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
+               $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
+               $status->merge( $masterStatus );
+               // Propagate the operations to the clone backends...
+               foreach ( $this->backends as $index => $backend ) {
+                       if ( $index !== $this->masterIndex ) { // not done already
+                               $realOps = $this->substOpBatchPaths( $ops, $backend );
+                               $status->merge( $backend->doQuickOperations( $realOps ) );
+                       }
+               }
+               // Make 'success', 'successCount', and 'failCount' fields reflect
+               // the overall operation, rather than all the batches for each backend.
+               // Do this by only using success values from the master backend's batch.
+               $status->success = $masterStatus->success;
+               $status->successCount = $masterStatus->successCount;
+               $status->failCount = $masterStatus->failCount;
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::doPrepare()
+        * @return Status
+        */
+       protected function doPrepare( array $params ) {
+               $status = Status::newGood();
+               foreach ( $this->backends as $backend ) {
+                       $realParams = $this->substOpPaths( $params, $backend );
+                       $status->merge( $backend->doPrepare( $realParams ) );
+               }
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::doSecure()
+        * @param $params array
+        * @return Status
+        */
+       protected function doSecure( array $params ) {
+               $status = Status::newGood();
+               foreach ( $this->backends as $backend ) {
+                       $realParams = $this->substOpPaths( $params, $backend );
+                       $status->merge( $backend->doSecure( $realParams ) );
+               }
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::doPublish()
+        * @param $params array
+        * @return Status
+        */
+       protected function doPublish( array $params ) {
+               $status = Status::newGood();
+               foreach ( $this->backends as $backend ) {
+                       $realParams = $this->substOpPaths( $params, $backend );
+                       $status->merge( $backend->doPublish( $realParams ) );
+               }
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::doClean()
+        * @param $params array
+        * @return Status
+        */
+       protected function doClean( array $params ) {
+               $status = Status::newGood();
+               foreach ( $this->backends as $backend ) {
+                       $realParams = $this->substOpPaths( $params, $backend );
+                       $status->merge( $backend->doClean( $realParams ) );
+               }
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::concatenate()
+        * @param $params array
+        * @return Status
+        */
+       public function concatenate( array $params ) {
+               // We are writing to an FS file, so we don't need to do this per-backend
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->concatenate( $realParams );
+       }
+
+       /**
+        * @see FileBackend::fileExists()
+        * @param $params array
+        */
+       public function fileExists( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->fileExists( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getFileTimestamp()
+        * @param $params array
+        * @return bool|string
+        */
+       public function getFileTimestamp( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getFileSize()
+        * @param $params array
+        * @return bool|int
+        */
+       public function getFileSize( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getFileSize( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getFileStat()
+        * @param $params array
+        * @return Array|bool|null
+        */
+       public function getFileStat( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getFileStat( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getFileContents()
+        * @param $params array
+        * @return bool|string
+        */
+       public function getFileContents( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getFileContents( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getFileSha1Base36()
+        * @param $params array
+        * @return bool|string
+        */
+       public function getFileSha1Base36( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getFileProps()
+        * @param $params array
+        * @return Array
+        */
+       public function getFileProps( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getFileProps( $realParams );
+       }
+
+       /**
+        * @see FileBackend::streamFile()
+        * @param $params array
+        * @return \Status
+        */
+       public function streamFile( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->streamFile( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getLocalReference()
+        * @param $params array
+        * @return FSFile|null
+        */
+       public function getLocalReference( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getLocalReference( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getLocalCopy()
+        * @param $params array
+        * @return null|TempFSFile
+        */
+       public function getLocalCopy( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getLocalCopy( $realParams );
+       }
+
+       /**
+        * @see FileBackend::directoryExists()
+        * @param $params array
+        * @return bool|null
+        */
+       public function directoryExists( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->directoryExists( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getSubdirectoryList()
+        * @param $params array
+        * @return Array|null|Traversable
+        */
+       public function getDirectoryList( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
+       }
+
+       /**
+        * @see FileBackend::getFileList()
+        * @param $params array
+        * @return Array|null|\Traversable
+        */
+       public function getFileList( array $params ) {
+               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+               return $this->backends[$this->masterIndex]->getFileList( $realParams );
+       }
+
+       /**
+        * @see FileBackend::clearCache()
+        */
+       public function clearCache( array $paths = null ) {
+               foreach ( $this->backends as $backend ) {
+                       $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
+                       $backend->clearCache( $realPaths );
+               }
+       }
+
+       /**
+        * @see FileBackend::getScopedLocksForOps()
+        */
+       public function getScopedLocksForOps( array $ops, Status $status ) {
+               $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $ops );
+               // Get the paths to lock from the master backend
+               $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
+               // Get the paths under the proxy backend's name
+               $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
+               $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
+               return array(
+                       $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
+                       $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
+               );
+       }
+}
diff --git a/includes/filebackend/FileBackendStore.php b/includes/filebackend/FileBackendStore.php
new file mode 100644 (file)
index 0000000..852a653
--- /dev/null
@@ -0,0 +1,1730 @@
+<?php
+/**
+ * Base class for all backends using particular storage medium.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Base class for all backends using particular storage medium.
+ *
+ * This class defines the methods as abstract that subclasses must implement.
+ * Outside callers should *not* use functions with "Internal" in the name.
+ *
+ * The FileBackend operations are implemented using basic functions
+ * such as storeInternal(), copyInternal(), deleteInternal() and the like.
+ * This class is also responsible for path resolution and sanitization.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+abstract class FileBackendStore extends FileBackend {
+       /** @var BagOStuff */
+       protected $memCache;
+       /** @var ProcessCacheLRU */
+       protected $cheapCache; // Map of paths to small (RAM/disk) cache items
+       /** @var ProcessCacheLRU */
+       protected $expensiveCache; // Map of paths to large (RAM/disk) cache items
+
+       /** @var Array Map of container names to sharding settings */
+       protected $shardViaHashLevels = array(); // (container name => config array)
+
+       protected $maxFileSize = 4294967296; // integer bytes (4GiB)
+
+       /**
+        * @see FileBackend::__construct()
+        *
+        * @param $config Array
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+               $this->memCache       = new EmptyBagOStuff(); // disabled by default
+               $this->cheapCache     = new ProcessCacheLRU( 300 );
+               $this->expensiveCache = new ProcessCacheLRU( 5 );
+       }
+
+       /**
+        * Get the maximum allowable file size given backend
+        * medium restrictions and basic performance constraints.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * @return integer Bytes
+        */
+       final public function maxFileSizeInternal() {
+               return $this->maxFileSize;
+       }
+
+       /**
+        * Check if a file can be created at a given storage path.
+        * FS backends should check if the parent directory exists and the file is writable.
+        * Backends using key/value stores should check if the container exists.
+        *
+        * @param $storagePath string
+        * @return bool
+        */
+       abstract public function isPathUsableInternal( $storagePath );
+
+       /**
+        * Create a file in the backend with the given contents.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * $params include:
+        *   - content       : the raw file contents
+        *   - dst           : destination storage path
+        *   - overwrite     : overwrite any file that exists at the destination
+        *   - async         : Status will be returned immediately if supported.
+        *                     If the status is OK, then its value field will be
+        *                     set to a FileBackendStoreOpHandle object.
+        *
+        * @param $params Array
+        * @return Status
+        */
+       final public function createInternal( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
+                       $status = Status::newFatal( 'backend-fail-maxsize',
+                               $params['dst'], $this->maxFileSizeInternal() );
+               } else {
+                       $status = $this->doCreateInternal( $params );
+                       $this->clearCache( array( $params['dst'] ) );
+                       $this->deleteFileCache( $params['dst'] ); // persistent cache
+               }
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::createInternal()
+        */
+       abstract protected function doCreateInternal( array $params );
+
+       /**
+        * Store a file into the backend from a file on disk.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * $params include:
+        *   - src           : source path on disk
+        *   - dst           : destination storage path
+        *   - overwrite     : overwrite any file that exists at the destination
+        *   - async         : Status will be returned immediately if supported.
+        *                     If the status is OK, then its value field will be
+        *                     set to a FileBackendStoreOpHandle object.
+        *
+        * @param $params Array
+        * @return Status
+        */
+       final public function storeInternal( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
+                       $status = Status::newFatal( 'backend-fail-maxsize',
+                               $params['dst'], $this->maxFileSizeInternal() );
+               } else {
+                       $status = $this->doStoreInternal( $params );
+                       $this->clearCache( array( $params['dst'] ) );
+                       $this->deleteFileCache( $params['dst'] ); // persistent cache
+               }
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::storeInternal()
+        */
+       abstract protected function doStoreInternal( array $params );
+
+       /**
+        * Copy a file from one storage path to another in the backend.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * $params include:
+        *   - src           : source storage path
+        *   - dst           : destination storage path
+        *   - overwrite     : overwrite any file that exists at the destination
+        *   - async         : Status will be returned immediately if supported.
+        *                     If the status is OK, then its value field will be
+        *                     set to a FileBackendStoreOpHandle object.
+        *
+        * @param $params Array
+        * @return Status
+        */
+       final public function copyInternal( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = $this->doCopyInternal( $params );
+               $this->clearCache( array( $params['dst'] ) );
+               $this->deleteFileCache( $params['dst'] ); // persistent cache
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::copyInternal()
+        */
+       abstract protected function doCopyInternal( array $params );
+
+       /**
+        * Delete a file at the storage path.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * $params include:
+        *   - src                 : source storage path
+        *   - ignoreMissingSource : do nothing if the source file does not exist
+        *   - async               : Status will be returned immediately if supported.
+        *                           If the status is OK, then its value field will be
+        *                           set to a FileBackendStoreOpHandle object.
+        *
+        * @param $params Array
+        * @return Status
+        */
+       final public function deleteInternal( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = $this->doDeleteInternal( $params );
+               $this->clearCache( array( $params['src'] ) );
+               $this->deleteFileCache( $params['src'] ); // persistent cache
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::deleteInternal()
+        */
+       abstract protected function doDeleteInternal( array $params );
+
+       /**
+        * Move a file from one storage path to another in the backend.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * $params include:
+        *   - src           : source storage path
+        *   - dst           : destination storage path
+        *   - overwrite     : overwrite any file that exists at the destination
+        *   - async         : Status will be returned immediately if supported.
+        *                     If the status is OK, then its value field will be
+        *                     set to a FileBackendStoreOpHandle object.
+        *
+        * @param $params Array
+        * @return Status
+        */
+       final public function moveInternal( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = $this->doMoveInternal( $params );
+               $this->clearCache( array( $params['src'], $params['dst'] ) );
+               $this->deleteFileCache( $params['src'] ); // persistent cache
+               $this->deleteFileCache( $params['dst'] ); // persistent cache
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::moveInternal()
+        * @return Status
+        */
+       protected function doMoveInternal( array $params ) {
+               unset( $params['async'] ); // two steps, won't work here :)
+               // Copy source to dest
+               $status = $this->copyInternal( $params );
+               if ( $status->isOK() ) {
+                       // Delete source (only fails due to races or medium going down)
+                       $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
+                       $status->setResult( true, $status->value ); // ignore delete() errors
+               }
+               return $status;
+       }
+
+       /**
+        * No-op file operation that does nothing.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * @param $params Array
+        * @return Status
+        */
+       final public function nullInternal( array $params ) {
+               return Status::newGood();
+       }
+
+       /**
+        * @see FileBackend::concatenate()
+        * @return Status
+        */
+       final public function concatenate( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = Status::newGood();
+
+               // Try to lock the source files for the scope of this function
+               $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
+               if ( $status->isOK() ) {
+                       // Actually do the concatenation
+                       $status->merge( $this->doConcatenate( $params ) );
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::concatenate()
+        * @return Status
+        */
+       protected function doConcatenate( array $params ) {
+               $status = Status::newGood();
+               $tmpPath = $params['dst']; // convenience
+
+               // Check that the specified temp file is valid...
+               wfSuppressWarnings();
+               $ok = ( is_file( $tmpPath ) && !filesize( $tmpPath ) );
+               wfRestoreWarnings();
+               if ( !$ok ) { // not present or not empty
+                       $status->fatal( 'backend-fail-opentemp', $tmpPath );
+                       return $status;
+               }
+
+               // Build up the temp file using the source chunks (in order)...
+               $tmpHandle = fopen( $tmpPath, 'ab' );
+               if ( $tmpHandle === false ) {
+                       $status->fatal( 'backend-fail-opentemp', $tmpPath );
+                       return $status;
+               }
+               foreach ( $params['srcs'] as $virtualSource ) {
+                       // Get a local FS version of the chunk
+                       $tmpFile = $this->getLocalReference( array( 'src' => $virtualSource ) );
+                       if ( !$tmpFile ) {
+                               $status->fatal( 'backend-fail-read', $virtualSource );
+                               return $status;
+                       }
+                       // Get a handle to the local FS version
+                       $sourceHandle = fopen( $tmpFile->getPath(), 'r' );
+                       if ( $sourceHandle === false ) {
+                               fclose( $tmpHandle );
+                               $status->fatal( 'backend-fail-read', $virtualSource );
+                               return $status;
+                       }
+                       // Append chunk to file (pass chunk size to avoid magic quotes)
+                       if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) {
+                               fclose( $sourceHandle );
+                               fclose( $tmpHandle );
+                               $status->fatal( 'backend-fail-writetemp', $tmpPath );
+                               return $status;
+                       }
+                       fclose( $sourceHandle );
+               }
+               if ( !fclose( $tmpHandle ) ) {
+                       $status->fatal( 'backend-fail-closetemp', $tmpPath );
+                       return $status;
+               }
+
+               clearstatcache(); // temp file changed
+
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::doPrepare()
+        * @return Status
+        */
+       final protected function doPrepare( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+
+               $status = Status::newGood();
+               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+               if ( $dir === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+                       wfProfileOut( __METHOD__ . '-' . $this->name );
+                       wfProfileOut( __METHOD__ );
+                       return $status; // invalid storage path
+               }
+
+               if ( $shard !== null ) { // confined to a single container/shard
+                       $status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) );
+               } else { // directory is on several shards
+                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+                               $status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) );
+                       }
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doPrepare()
+        * @return Status
+        */
+       protected function doPrepareInternal( $container, $dir, array $params ) {
+               return Status::newGood();
+       }
+
+       /**
+        * @see FileBackend::doSecure()
+        * @return Status
+        */
+       final protected function doSecure( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = Status::newGood();
+
+               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+               if ( $dir === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+                       wfProfileOut( __METHOD__ . '-' . $this->name );
+                       wfProfileOut( __METHOD__ );
+                       return $status; // invalid storage path
+               }
+
+               if ( $shard !== null ) { // confined to a single container/shard
+                       $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
+               } else { // directory is on several shards
+                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+                               $status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) );
+                       }
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doSecure()
+        * @return Status
+        */
+       protected function doSecureInternal( $container, $dir, array $params ) {
+               return Status::newGood();
+       }
+
+       /**
+        * @see FileBackend::doPublish()
+        * @return Status
+        */
+       final protected function doPublish( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = Status::newGood();
+
+               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+               if ( $dir === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+                       wfProfileOut( __METHOD__ . '-' . $this->name );
+                       wfProfileOut( __METHOD__ );
+                       return $status; // invalid storage path
+               }
+
+               if ( $shard !== null ) { // confined to a single container/shard
+                       $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
+               } else { // directory is on several shards
+                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+                               $status->merge( $this->doPublishInternal( "{$fullCont}{$suffix}", $dir, $params ) );
+                       }
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doPublish()
+        * @return Status
+        */
+       protected function doPublishInternal( $container, $dir, array $params ) {
+               return Status::newGood();
+       }
+
+       /**
+        * @see FileBackend::doClean()
+        * @return Status
+        */
+       final protected function doClean( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = Status::newGood();
+
+               // Recursive: first delete all empty subdirs recursively
+               if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) {
+                       $subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) );
+                       if ( $subDirsRel !== null ) { // no errors
+                               foreach ( $subDirsRel as $subDirRel ) {
+                                       $subDir = $params['dir'] . "/{$subDirRel}"; // full path
+                                       $status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) );
+                               }
+                       }
+               }
+
+               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+               if ( $dir === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+                       wfProfileOut( __METHOD__ . '-' . $this->name );
+                       wfProfileOut( __METHOD__ );
+                       return $status; // invalid storage path
+               }
+
+               // Attempt to lock this directory...
+               $filesLockEx = array( $params['dir'] );
+               $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
+               if ( !$status->isOK() ) {
+                       wfProfileOut( __METHOD__ . '-' . $this->name );
+                       wfProfileOut( __METHOD__ );
+                       return $status; // abort
+               }
+
+               if ( $shard !== null ) { // confined to a single container/shard
+                       $status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) );
+                       $this->deleteContainerCache( $fullCont ); // purge cache
+               } else { // directory is on several shards
+                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+                               $status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) );
+                               $this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache
+                       }
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doClean()
+        * @return Status
+        */
+       protected function doCleanInternal( $container, $dir, array $params ) {
+               return Status::newGood();
+       }
+
+       /**
+        * @see FileBackend::fileExists()
+        * @return bool|null
+        */
+       final public function fileExists( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $stat = $this->getFileStat( $params );
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return ( $stat === null ) ? null : (bool)$stat; // null => failure
+       }
+
+       /**
+        * @see FileBackend::getFileTimestamp()
+        * @return bool
+        */
+       final public function getFileTimestamp( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $stat = $this->getFileStat( $params );
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $stat ? $stat['mtime'] : false;
+       }
+
+       /**
+        * @see FileBackend::getFileSize()
+        * @return bool
+        */
+       final public function getFileSize( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $stat = $this->getFileStat( $params );
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $stat ? $stat['size'] : false;
+       }
+
+       /**
+        * @see FileBackend::getFileStat()
+        * @return bool
+        */
+       final public function getFileStat( array $params ) {
+               $path = self::normalizeStoragePath( $params['src'] );
+               if ( $path === null ) {
+                       return false; // invalid storage path
+               }
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $latest = !empty( $params['latest'] ); // use latest data?
+               if ( !$this->cheapCache->has( $path, 'stat' ) ) {
+                       $this->primeFileCache( array( $path ) ); // check persistent cache
+               }
+               if ( $this->cheapCache->has( $path, 'stat' ) ) {
+                       $stat = $this->cheapCache->get( $path, 'stat' );
+                       // If we want the latest data, check that this cached
+                       // value was in fact fetched with the latest available data.
+                       if ( !$latest || $stat['latest'] ) {
+                               wfProfileOut( __METHOD__ . '-' . $this->name );
+                               wfProfileOut( __METHOD__ );
+                               return $stat;
+                       }
+               }
+               wfProfileIn( __METHOD__ . '-miss' );
+               wfProfileIn( __METHOD__ . '-miss-' . $this->name );
+               $stat = $this->doGetFileStat( $params );
+               wfProfileOut( __METHOD__ . '-miss-' . $this->name );
+               wfProfileOut( __METHOD__ . '-miss' );
+               if ( is_array( $stat ) ) { // don't cache negatives
+                       $stat['latest'] = $latest;
+                       $this->cheapCache->set( $path, 'stat', $stat );
+                       $this->setFileCache( $path, $stat ); // update persistent cache
+                       if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
+                               $this->cheapCache->set( $path, 'sha1',
+                                       array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
+                       }
+               } else {
+                       wfDebug( __METHOD__ . ": File $path does not exist.\n" );
+               }
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $stat;
+       }
+
+       /**
+        * @see FileBackendStore::getFileStat()
+        */
+       abstract protected function doGetFileStat( array $params );
+
+       /**
+        * @see FileBackend::getFileContents()
+        * @return bool|string
+        */
+       public function getFileContents( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $tmpFile = $this->getLocalReference( $params );
+               if ( !$tmpFile ) {
+                       wfProfileOut( __METHOD__ . '-' . $this->name );
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+               wfSuppressWarnings();
+               $data = file_get_contents( $tmpFile->getPath() );
+               wfRestoreWarnings();
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $data;
+       }
+
+       /**
+        * @see FileBackend::getFileSha1Base36()
+        * @return bool|string
+        */
+       final public function getFileSha1Base36( array $params ) {
+               $path = self::normalizeStoragePath( $params['src'] );
+               if ( $path === null ) {
+                       return false; // invalid storage path
+               }
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $latest = !empty( $params['latest'] ); // use latest data?
+               if ( $this->cheapCache->has( $path, 'sha1' ) ) {
+                       $stat = $this->cheapCache->get( $path, 'sha1' );
+                       // If we want the latest data, check that this cached
+                       // value was in fact fetched with the latest available data.
+                       if ( !$latest || $stat['latest'] ) {
+                               wfProfileOut( __METHOD__ . '-' . $this->name );
+                               wfProfileOut( __METHOD__ );
+                               return $stat['hash'];
+                       }
+               }
+               wfProfileIn( __METHOD__ . '-miss' );
+               wfProfileIn( __METHOD__ . '-miss-' . $this->name );
+               $hash = $this->doGetFileSha1Base36( $params );
+               wfProfileOut( __METHOD__ . '-miss-' . $this->name );
+               wfProfileOut( __METHOD__ . '-miss' );
+               if ( $hash ) { // don't cache negatives
+                       $this->cheapCache->set( $path, 'sha1',
+                               array( 'hash' => $hash, 'latest' => $latest ) );
+               }
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $hash;
+       }
+
+       /**
+        * @see FileBackendStore::getFileSha1Base36()
+        * @return bool|string
+        */
+       protected function doGetFileSha1Base36( array $params ) {
+               $fsFile = $this->getLocalReference( $params );
+               if ( !$fsFile ) {
+                       return false;
+               } else {
+                       return $fsFile->getSha1Base36();
+               }
+       }
+
+       /**
+        * @see FileBackend::getFileProps()
+        * @return Array
+        */
+       final public function getFileProps( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $fsFile = $this->getLocalReference( $params );
+               $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $props;
+       }
+
+       /**
+        * @see FileBackend::getLocalReference()
+        * @return TempFSFile|null
+        */
+       public function getLocalReference( array $params ) {
+               $path = self::normalizeStoragePath( $params['src'] );
+               if ( $path === null ) {
+                       return null; // invalid storage path
+               }
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $latest = !empty( $params['latest'] ); // use latest data?
+               if ( $this->expensiveCache->has( $path, 'localRef' ) ) {
+                       $val = $this->expensiveCache->get( $path, 'localRef' );
+                       // If we want the latest data, check that this cached
+                       // value was in fact fetched with the latest available data.
+                       if ( !$latest || $val['latest'] ) {
+                               wfProfileOut( __METHOD__ . '-' . $this->name );
+                               wfProfileOut( __METHOD__ );
+                               return $val['object'];
+                       }
+               }
+               $tmpFile = $this->getLocalCopy( $params );
+               if ( $tmpFile ) { // don't cache negatives
+                       $this->expensiveCache->set( $path, 'localRef',
+                               array( 'object' => $tmpFile, 'latest' => $latest ) );
+               }
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $tmpFile;
+       }
+
+       /**
+        * @see FileBackend::streamFile()
+        * @return Status
+        */
+       final public function streamFile( array $params ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = Status::newGood();
+
+               $info = $this->getFileStat( $params );
+               if ( !$info ) { // let StreamFile handle the 404
+                       $status->fatal( 'backend-fail-notexists', $params['src'] );
+               }
+
+               // Set output buffer and HTTP headers for stream
+               $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array();
+               $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders );
+               if ( $res == StreamFile::NOT_MODIFIED ) {
+                       // do nothing; client cache is up to date
+               } elseif ( $res == StreamFile::READY_STREAM ) {
+                       wfProfileIn( __METHOD__ . '-send' );
+                       wfProfileIn( __METHOD__ . '-send-' . $this->name );
+                       $status = $this->doStreamFile( $params );
+                       wfProfileOut( __METHOD__ . '-send-' . $this->name );
+                       wfProfileOut( __METHOD__ . '-send' );
+               } else {
+                       $status->fatal( 'backend-fail-stream', $params['src'] );
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::streamFile()
+        * @return Status
+        */
+       protected function doStreamFile( array $params ) {
+               $status = Status::newGood();
+
+               $fsFile = $this->getLocalReference( $params );
+               if ( !$fsFile ) {
+                       $status->fatal( 'backend-fail-stream', $params['src'] );
+               } elseif ( !readfile( $fsFile->getPath() ) ) {
+                       $status->fatal( 'backend-fail-stream', $params['src'] );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::directoryExists()
+        * @return bool|null
+        */
+       final public function directoryExists( array $params ) {
+               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+               if ( $dir === null ) {
+                       return false; // invalid storage path
+               }
+               if ( $shard !== null ) { // confined to a single container/shard
+                       return $this->doDirectoryExists( $fullCont, $dir, $params );
+               } else { // directory is on several shards
+                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+                       $res = false; // response
+                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+                               $exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
+                               if ( $exists ) {
+                                       $res = true;
+                                       break; // found one!
+                               } elseif ( $exists === null ) { // error?
+                                       $res = null; // if we don't find anything, it is indeterminate
+                               }
+                       }
+                       return $res;
+               }
+       }
+
+       /**
+        * @see FileBackendStore::directoryExists()
+        *
+        * @param $container string Resolved container name
+        * @param $dir string Resolved path relative to container
+        * @param $params Array
+        * @return bool|null
+        */
+       abstract protected function doDirectoryExists( $container, $dir, array $params );
+
+       /**
+        * @see FileBackend::getDirectoryList()
+        * @return Traversable|Array|null Returns null on failure
+        */
+       final public function getDirectoryList( array $params ) {
+               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+               if ( $dir === null ) { // invalid storage path
+                       return null;
+               }
+               if ( $shard !== null ) {
+                       // File listing is confined to a single container/shard
+                       return $this->getDirectoryListInternal( $fullCont, $dir, $params );
+               } else {
+                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+                       // File listing spans multiple containers/shards
+                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+                       return new FileBackendStoreShardDirIterator( $this,
+                               $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
+               }
+       }
+
+       /**
+        * Do not call this function from places outside FileBackend
+        *
+        * @see FileBackendStore::getDirectoryList()
+        *
+        * @param $container string Resolved container name
+        * @param $dir string Resolved path relative to container
+        * @param $params Array
+        * @return Traversable|Array|null Returns null on failure
+        */
+       abstract public function getDirectoryListInternal( $container, $dir, array $params );
+
+       /**
+        * @see FileBackend::getFileList()
+        * @return Traversable|Array|null Returns null on failure
+        */
+       final public function getFileList( array $params ) {
+               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+               if ( $dir === null ) { // invalid storage path
+                       return null;
+               }
+               if ( $shard !== null ) {
+                       // File listing is confined to a single container/shard
+                       return $this->getFileListInternal( $fullCont, $dir, $params );
+               } else {
+                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+                       // File listing spans multiple containers/shards
+                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+                       return new FileBackendStoreShardFileIterator( $this,
+                               $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
+               }
+       }
+
+       /**
+        * Do not call this function from places outside FileBackend
+        *
+        * @see FileBackendStore::getFileList()
+        *
+        * @param $container string Resolved container name
+        * @param $dir string Resolved path relative to container
+        * @param $params Array
+        * @return Traversable|Array|null Returns null on failure
+        */
+       abstract public function getFileListInternal( $container, $dir, array $params );
+
+       /**
+        * Return a list of FileOp objects from a list of operations.
+        * Do not call this function from places outside FileBackend.
+        *
+        * The result must have the same number of items as the input.
+        * An exception is thrown if an unsupported operation is requested.
+        *
+        * @param $ops Array Same format as doOperations()
+        * @return Array List of FileOp objects
+        * @throws MWException
+        */
+       final public function getOperationsInternal( array $ops ) {
+               $supportedOps = array(
+                       'store'       => 'StoreFileOp',
+                       'copy'        => 'CopyFileOp',
+                       'move'        => 'MoveFileOp',
+                       'delete'      => 'DeleteFileOp',
+                       'create'      => 'CreateFileOp',
+                       'null'        => 'NullFileOp'
+               );
+
+               $performOps = array(); // array of FileOp objects
+               // Build up ordered array of FileOps...
+               foreach ( $ops as $operation ) {
+                       $opName = $operation['op'];
+                       if ( isset( $supportedOps[$opName] ) ) {
+                               $class = $supportedOps[$opName];
+                               // Get params for this operation
+                               $params = $operation;
+                               // Append the FileOp class
+                               $performOps[] = new $class( $this, $params );
+                       } else {
+                               throw new MWException( "Operation '$opName' is not supported." );
+                       }
+               }
+
+               return $performOps;
+       }
+
+       /**
+        * Get a list of storage paths to lock for a list of operations
+        * Returns an array with 'sh' (shared) and 'ex' (exclusive) keys,
+        * each corresponding to a list of storage paths to be locked.
+        *
+        * @param $performOps Array List of FileOp objects
+        * @return Array ('sh' => list of paths, 'ex' => list of paths)
+        */
+       final public function getPathsToLockForOpsInternal( array $performOps ) {
+               // Build up a list of files to lock...
+               $paths = array( 'sh' => array(), 'ex' => array() );
+               foreach ( $performOps as $fileOp ) {
+                       $paths['sh'] = array_merge( $paths['sh'], $fileOp->storagePathsRead() );
+                       $paths['ex'] = array_merge( $paths['ex'], $fileOp->storagePathsChanged() );
+               }
+               // Optimization: if doing an EX lock anyway, don't also set an SH one
+               $paths['sh'] = array_diff( $paths['sh'], $paths['ex'] );
+               // Get a shared lock on the parent directory of each path changed
+               $paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) );
+
+               return $paths;
+       }
+
+       /**
+        * @see FileBackend::getScopedLocksForOps()
+        * @return Array
+        */
+       public function getScopedLocksForOps( array $ops, Status $status ) {
+               $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
+               return array(
+                       $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
+                       $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
+               );
+       }
+
+       /**
+        * @see FileBackend::doOperationsInternal()
+        * @return Status
+        */
+       final protected function doOperationsInternal( array $ops, array $opts ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = Status::newGood();
+
+               // Build up a list of FileOps...
+               $performOps = $this->getOperationsInternal( $ops );
+
+               // Acquire any locks as needed...
+               if ( empty( $opts['nonLocking'] ) ) {
+                       // Build up a list of files to lock...
+                       $paths = $this->getPathsToLockForOpsInternal( $performOps );
+                       // Try to lock those files for the scope of this function...
+                       $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
+                       $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
+                       if ( !$status->isOK() ) {
+                               wfProfileOut( __METHOD__ . '-' . $this->name );
+                               wfProfileOut( __METHOD__ );
+                               return $status; // abort
+                       }
+               }
+
+               // Clear any file cache entries (after locks acquired)
+               $this->clearCache();
+
+               // Load from the persistent file and container caches
+               $this->primeFileCache( $performOps );
+               $this->primeContainerCache( $performOps );
+
+               // Actually attempt the operation batch...
+               $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
+
+               // Merge errors into status fields
+               $status->merge( $subStatus );
+               $status->success = $subStatus->success; // not done in merge()
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * @see FileBackend::doQuickOperationsInternal()
+        * @return Status
+        * @throws MWException
+        */
+       final protected function doQuickOperationsInternal( array $ops ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               $status = Status::newGood();
+
+               $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
+               $async = ( $this->parallelize === 'implicit' );
+               $maxConcurrency = $this->concurrency; // throttle
+
+               $statuses = array(); // array of (index => Status)
+               $fileOpHandles = array(); // list of (index => handle) arrays
+               $curFileOpHandles = array(); // current handle batch
+               // Perform the sync-only ops and build up op handles for the async ops...
+               foreach ( $ops as $index => $params ) {
+                       if ( !in_array( $params['op'], $supportedOps ) ) {
+                               wfProfileOut( __METHOD__ . '-' . $this->name );
+                               wfProfileOut( __METHOD__ );
+                               throw new MWException( "Operation '{$params['op']}' is not supported." );
+                       }
+                       $method = $params['op'] . 'Internal'; // e.g. "storeInternal"
+                       $subStatus = $this->$method( array( 'async' => $async ) + $params );
+                       if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async
+                               if ( count( $curFileOpHandles ) >= $maxConcurrency ) {
+                                       $fileOpHandles[] = $curFileOpHandles; // push this batch
+                                       $curFileOpHandles = array();
+                               }
+                               $curFileOpHandles[$index] = $subStatus->value; // keep index
+                       } else { // error or completed
+                               $statuses[$index] = $subStatus; // keep index
+                       }
+               }
+               if ( count( $curFileOpHandles ) ) {
+                       $fileOpHandles[] = $curFileOpHandles; // last batch
+               }
+               // Do all the async ops that can be done concurrently...
+               foreach ( $fileOpHandles as $fileHandleBatch ) {
+                       $statuses = $statuses + $this->executeOpHandlesInternal( $fileHandleBatch );
+               }
+               // Marshall and merge all the responses...
+               foreach ( $statuses as $index => $subStatus ) {
+                       $status->merge( $subStatus );
+                       if ( $subStatus->isOK() ) {
+                               $status->success[$index] = true;
+                               ++$status->successCount;
+                       } else {
+                               $status->success[$index] = false;
+                               ++$status->failCount;
+                       }
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * Execute a list of FileBackendStoreOpHandle handles in parallel.
+        * The resulting Status object fields will correspond
+        * to the order in which the handles where given.
+        *
+        * @param $handles Array List of FileBackendStoreOpHandle objects
+        * @return Array Map of Status objects
+        * @throws MWException
+        */
+       final public function executeOpHandlesInternal( array $fileOpHandles ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+               foreach ( $fileOpHandles as $fileOpHandle ) {
+                       if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
+                               throw new MWException( "Given a non-FileBackendStoreOpHandle object." );
+                       } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
+                               throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." );
+                       }
+               }
+               $res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
+               foreach ( $fileOpHandles as $fileOpHandle ) {
+                       $fileOpHandle->closeResources();
+               }
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+               return $res;
+       }
+
+       /**
+        * @see FileBackendStore::executeOpHandlesInternal()
+        * @return Array List of corresponding Status objects
+        */
+       protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
+               foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty
+                       throw new MWException( "This backend supports no asynchronous operations." );
+               }
+               return array();
+       }
+
+       /**
+        * @see FileBackend::clearCache()
+        */
+       final public function clearCache( array $paths = null ) {
+               if ( is_array( $paths ) ) {
+                       $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
+                       $paths = array_filter( $paths, 'strlen' ); // remove nulls
+               }
+               if ( $paths === null ) {
+                       $this->cheapCache->clear();
+                       $this->expensiveCache->clear();
+               } else {
+                       foreach ( $paths as $path ) {
+                               $this->cheapCache->clear( $path );
+                               $this->expensiveCache->clear( $path );
+                       }
+               }
+               $this->doClearCache( $paths );
+       }
+
+       /**
+        * Clears any additional stat caches for storage paths
+        *
+        * @see FileBackend::clearCache()
+        *
+        * @param $paths Array Storage paths (optional)
+        * @return void
+        */
+       protected function doClearCache( array $paths = null ) {}
+
+       /**
+        * Is this a key/value store where directories are just virtual?
+        * Virtual directories exists in so much as files exists that are
+        * prefixed with the directory path followed by a forward slash.
+        *
+        * @return bool
+        */
+       abstract protected function directoriesAreVirtual();
+
+       /**
+        * Check if a container name is valid.
+        * This checks for for length and illegal characters.
+        *
+        * @param $container string
+        * @return bool
+        */
+       final protected static function isValidContainerName( $container ) {
+               // This accounts for Swift and S3 restrictions while leaving room
+               // for things like '.xxx' (hex shard chars) or '.seg' (segments).
+               // This disallows directory separators or traversal characters.
+               // Note that matching strings URL encode to the same string;
+               // in Swift, the length restriction is *after* URL encoding.
+               return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container );
+       }
+
+       /**
+        * Splits a storage path into an internal container name,
+        * an internal relative file name, and a container shard suffix.
+        * Any shard suffix is already appended to the internal container name.
+        * This also checks that the storage path is valid and within this backend.
+        *
+        * If the container is sharded but a suffix could not be determined,
+        * this means that the path can only refer to a directory and can only
+        * be scanned by looking in all the container shards.
+        *
+        * @param $storagePath string
+        * @return Array (container, path, container suffix) or (null, null, null) if invalid
+        */
+       final protected function resolveStoragePath( $storagePath ) {
+               list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
+               if ( $backend === $this->name ) { // must be for this backend
+                       $relPath = self::normalizeContainerPath( $relPath );
+                       if ( $relPath !== null ) {
+                               // Get shard for the normalized path if this container is sharded
+                               $cShard = $this->getContainerShard( $container, $relPath );
+                               // Validate and sanitize the relative path (backend-specific)
+                               $relPath = $this->resolveContainerPath( $container, $relPath );
+                               if ( $relPath !== null ) {
+                                       // Prepend any wiki ID prefix to the container name
+                                       $container = $this->fullContainerName( $container );
+                                       if ( self::isValidContainerName( $container ) ) {
+                                               // Validate and sanitize the container name (backend-specific)
+                                               $container = $this->resolveContainerName( "{$container}{$cShard}" );
+                                               if ( $container !== null ) {
+                                                       return array( $container, $relPath, $cShard );
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return array( null, null, null );
+       }
+
+       /**
+        * Like resolveStoragePath() except null values are returned if
+        * the container is sharded and the shard could not be determined.
+        *
+        * @see FileBackendStore::resolveStoragePath()
+        *
+        * @param $storagePath string
+        * @return Array (container, path) or (null, null) if invalid
+        */
+       final protected function resolveStoragePathReal( $storagePath ) {
+               list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
+               if ( $cShard !== null ) {
+                       return array( $container, $relPath );
+               }
+               return array( null, null );
+       }
+
+       /**
+        * Get the container name shard suffix for a given path.
+        * Any empty suffix means the container is not sharded.
+        *
+        * @param $container string Container name
+        * @param $relPath string Storage path relative to the container
+        * @return string|null Returns null if shard could not be determined
+        */
+       final protected function getContainerShard( $container, $relPath ) {
+               list( $levels, $base, $repeat ) = $this->getContainerHashLevels( $container );
+               if ( $levels == 1 || $levels == 2 ) {
+                       // Hash characters are either base 16 or 36
+                       $char = ( $base == 36 ) ? '[0-9a-z]' : '[0-9a-f]';
+                       // Get a regex that represents the shard portion of paths.
+                       // The concatenation of the captures gives us the shard.
+                       if ( $levels === 1 ) { // 16 or 36 shards per container
+                               $hashDirRegex = '(' . $char . ')';
+                       } else { // 256 or 1296 shards per container
+                               if ( $repeat ) { // verbose hash dir format (e.g. "a/ab/abc")
+                                       $hashDirRegex = $char . '/(' . $char . '{2})';
+                               } else { // short hash dir format (e.g. "a/b/c")
+                                       $hashDirRegex = '(' . $char . ')/(' . $char . ')';
+                               }
+                       }
+                       // Allow certain directories to be above the hash dirs so as
+                       // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab").
+                       // They must be 2+ chars to avoid any hash directory ambiguity.
+                       $m = array();
+                       if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) {
+                               return '.' . implode( '', array_slice( $m, 1 ) );
+                       }
+                       return null; // failed to match
+               }
+               return ''; // no sharding
+       }
+
+       /**
+        * Check if a storage path maps to a single shard.
+        * Container dirs like "a", where the container shards on "x/xy",
+        * can reside on several shards. Such paths are tricky to handle.
+        *
+        * @param $storagePath string Storage path
+        * @return bool
+        */
+       final public function isSingleShardPathInternal( $storagePath ) {
+               list( $c, $r, $shard ) = $this->resolveStoragePath( $storagePath );
+               return ( $shard !== null );
+       }
+
+       /**
+        * Get the sharding config for a container.
+        * If greater than 0, then all file storage paths within
+        * the container are required to be hashed accordingly.
+        *
+        * @param $container string
+        * @return Array (integer levels, integer base, repeat flag) or (0, 0, false)
+        */
+       final protected function getContainerHashLevels( $container ) {
+               if ( isset( $this->shardViaHashLevels[$container] ) ) {
+                       $config = $this->shardViaHashLevels[$container];
+                       $hashLevels = (int)$config['levels'];
+                       if ( $hashLevels == 1 || $hashLevels == 2 ) {
+                               $hashBase = (int)$config['base'];
+                               if ( $hashBase == 16 || $hashBase == 36 ) {
+                                       return array( $hashLevels, $hashBase, $config['repeat'] );
+                               }
+                       }
+               }
+               return array( 0, 0, false ); // no sharding
+       }
+
+       /**
+        * Get a list of full container shard suffixes for a container
+        *
+        * @param $container string
+        * @return Array
+        */
+       final protected function getContainerSuffixes( $container ) {
+               $shards = array();
+               list( $digits, $base ) = $this->getContainerHashLevels( $container );
+               if ( $digits > 0 ) {
+                       $numShards = pow( $base, $digits );
+                       for ( $index = 0; $index < $numShards; $index++ ) {
+                               $shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits );
+                       }
+               }
+               return $shards;
+       }
+
+       /**
+        * Get the full container name, including the wiki ID prefix
+        *
+        * @param $container string
+        * @return string
+        */
+       final protected function fullContainerName( $container ) {
+               if ( $this->wikiId != '' ) {
+                       return "{$this->wikiId}-$container";
+               } else {
+                       return $container;
+               }
+       }
+
+       /**
+        * Resolve a container name, checking if it's allowed by the backend.
+        * This is intended for internal use, such as encoding illegal chars.
+        * Subclasses can override this to be more restrictive.
+        *
+        * @param $container string
+        * @return string|null
+        */
+       protected function resolveContainerName( $container ) {
+               return $container;
+       }
+
+       /**
+        * Resolve a relative storage path, checking if it's allowed by the backend.
+        * This is intended for internal use, such as encoding illegal chars or perhaps
+        * getting absolute paths (e.g. FS based backends). Note that the relative path
+        * may be the empty string (e.g. the path is simply to the container).
+        *
+        * @param $container string Container name
+        * @param $relStoragePath string Storage path relative to the container
+        * @return string|null Path or null if not valid
+        */
+       protected function resolveContainerPath( $container, $relStoragePath ) {
+               return $relStoragePath;
+       }
+
+       /**
+        * Get the cache key for a container
+        *
+        * @param $container string Resolved container name
+        * @return string
+        */
+       private function containerCacheKey( $container ) {
+               return wfMemcKey( 'backend', $this->getName(), 'container', $container );
+       }
+
+       /**
+        * Set the cached info for a container
+        *
+        * @param $container string Resolved container name
+        * @param $val mixed Information to cache
+        */
+       final protected function setContainerCache( $container, $val ) {
+               $this->memCache->add( $this->containerCacheKey( $container ), $val, 14*86400 );
+       }
+
+       /**
+        * Delete the cached info for a container.
+        * The cache key is salted for a while to prevent race conditions.
+        *
+        * @param $container string Resolved container name
+        */
+       final protected function deleteContainerCache( $container ) {
+               if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) {
+                       trigger_error( "Unable to delete stat cache for container $container." );
+               }
+       }
+
+       /**
+        * Do a batch lookup from cache for container stats for all containers
+        * used in a list of container names, storage paths, or FileOp objects.
+        *
+        * @param $items Array
+        * @return void
+        */
+       final protected function primeContainerCache( array $items ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+
+               $paths = array(); // list of storage paths
+               $contNames = array(); // (cache key => resolved container name)
+               // Get all the paths/containers from the items...
+               foreach ( $items as $item ) {
+                       if ( $item instanceof FileOp ) {
+                               $paths = array_merge( $paths, $item->storagePathsRead() );
+                               $paths = array_merge( $paths, $item->storagePathsChanged() );
+                       } elseif ( self::isStoragePath( $item ) ) {
+                               $paths[] = $item;
+                       } elseif ( is_string( $item ) ) { // full container name
+                               $contNames[$this->containerCacheKey( $item )] = $item;
+                       }
+               }
+               // Get all the corresponding cache keys for paths...
+               foreach ( $paths as $path ) {
+                       list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path );
+                       if ( $fullCont !== null ) { // valid path for this backend
+                               $contNames[$this->containerCacheKey( $fullCont )] = $fullCont;
+                       }
+               }
+
+               $contInfo = array(); // (resolved container name => cache value)
+               // Get all cache entries for these container cache keys...
+               $values = $this->memCache->getMulti( array_keys( $contNames ) );
+               foreach ( $values as $cacheKey => $val ) {
+                       $contInfo[$contNames[$cacheKey]] = $val;
+               }
+
+               // Populate the container process cache for the backend...
+               $this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Fill the backend-specific process cache given an array of
+        * resolved container names and their corresponding cached info.
+        * Only containers that actually exist should appear in the map.
+        *
+        * @param $containerInfo Array Map of resolved container names to cached info
+        * @return void
+        */
+       protected function doPrimeContainerCache( array $containerInfo ) {}
+
+       /**
+        * Get the cache key for a file path
+        *
+        * @param $path string Storage path
+        * @return string
+        */
+       private function fileCacheKey( $path ) {
+               return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) );
+       }
+
+       /**
+        * Set the cached stat info for a file path
+        *
+        * @param $path string Storage path
+        * @param $val mixed Information to cache
+        */
+       final protected function setFileCache( $path, $val ) {
+               $this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 );
+       }
+
+       /**
+        * Delete the cached stat info for a file path.
+        * The cache key is salted for a while to prevent race conditions.
+        *
+        * @param $path string Storage path
+        */
+       final protected function deleteFileCache( $path ) {
+               if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) {
+                       trigger_error( "Unable to delete stat cache for file $path." );
+               }
+       }
+
+       /**
+        * Do a batch lookup from cache for file stats for all paths
+        * used in a list of storage paths or FileOp objects.
+        *
+        * @param $items Array List of storage paths or FileOps
+        * @return void
+        */
+       final protected function primeFileCache( array $items ) {
+               wfProfileIn( __METHOD__ );
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+
+               $paths = array(); // list of storage paths
+               $pathNames = array(); // (cache key => storage path)
+               // Get all the paths/containers from the items...
+               foreach ( $items as $item ) {
+                       if ( $item instanceof FileOp ) {
+                               $paths = array_merge( $paths, $item->storagePathsRead() );
+                               $paths = array_merge( $paths, $item->storagePathsChanged() );
+                       } elseif ( self::isStoragePath( $item ) ) {
+                               $paths[] = $item;
+                       }
+               }
+               // Get all the corresponding cache keys for paths...
+               foreach ( $paths as $path ) {
+                       list( $cont, $rel, $s ) = $this->resolveStoragePath( $path );
+                       if ( $rel !== null ) { // valid path for this backend
+                               $pathNames[$this->fileCacheKey( $path )] = $path;
+                       }
+               }
+               // Get all cache entries for these container cache keys...
+               $values = $this->memCache->getMulti( array_keys( $pathNames ) );
+               foreach ( $values as $cacheKey => $val ) {
+                       if ( is_array( $val ) ) {
+                               $path = $pathNames[$cacheKey];
+                               $this->cheapCache->set( $path, 'stat', $val );
+                               if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
+                                       $this->cheapCache->set( $path, 'sha1',
+                                               array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) );
+                               }
+                       }
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               wfProfileOut( __METHOD__ );
+       }
+}
+
+/**
+ * FileBackendStore helper class for performing asynchronous file operations.
+ *
+ * For example, calling FileBackendStore::createInternal() with the "async"
+ * param flag may result in a Status that contains this object as a value.
+ * This class is largely backend-specific and is mostly just "magic" to be
+ * passed to FileBackendStore::executeOpHandlesInternal().
+ */
+abstract class FileBackendStoreOpHandle {
+       /** @var Array */
+       public $params = array(); // params to caller functions
+       /** @var FileBackendStore */
+       public $backend;
+       /** @var Array */
+       public $resourcesToClose = array();
+
+       public $call; // string; name that identifies the function called
+
+       /**
+        * Close all open file handles
+        *
+        * @return void
+        */
+       public function closeResources() {
+               array_map( 'fclose', $this->resourcesToClose );
+       }
+}
+
+/**
+ * FileBackendStore helper function to handle listings that span container shards.
+ * Do not use this class from places outside of FileBackendStore.
+ *
+ * @ingroup FileBackend
+ */
+abstract class FileBackendStoreShardListIterator implements Iterator {
+       /** @var FileBackendStore */
+       protected $backend;
+       /** @var Array */
+       protected $params;
+       /** @var Array */
+       protected $shardSuffixes;
+       protected $container; // string; full container name
+       protected $directory; // string; resolved relative path
+
+       /** @var Traversable */
+       protected $iter;
+       protected $curShard = 0; // integer
+       protected $pos = 0; // integer
+
+       /** @var Array */
+       protected $multiShardPaths = array(); // (rel path => 1)
+
+       /**
+        * @param $backend FileBackendStore
+        * @param $container string Full storage container name
+        * @param $dir string Storage directory relative to container
+        * @param $suffixes Array List of container shard suffixes
+        * @param $params Array
+        */
+       public function __construct(
+               FileBackendStore $backend, $container, $dir, array $suffixes, array $params
+       ) {
+               $this->backend = $backend;
+               $this->container = $container;
+               $this->directory = $dir;
+               $this->shardSuffixes = $suffixes;
+               $this->params = $params;
+       }
+
+       /**
+        * @see Iterator::key()
+        * @return integer
+        */
+       public function key() {
+               return $this->pos;
+       }
+
+       /**
+        * @see Iterator::valid()
+        * @return bool
+        */
+       public function valid() {
+               if ( $this->iter instanceof Iterator ) {
+                       return $this->iter->valid();
+               } elseif ( is_array( $this->iter ) ) {
+                       return ( current( $this->iter ) !== false ); // no paths can have this value
+               }
+               return false; // some failure?
+       }
+
+       /**
+        * @see Iterator::current()
+        * @return string|bool String or false
+        */
+       public function current() {
+               return ( $this->iter instanceof Iterator )
+                       ? $this->iter->current()
+                       : current( $this->iter );
+       }
+
+       /**
+        * @see Iterator::next()
+        * @return void
+        */
+       public function next() {
+               ++$this->pos;
+               ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
+               do {
+                       $continue = false; // keep scanning shards?
+                       $this->filterViaNext(); // filter out duplicates
+                       // Find the next non-empty shard if no elements are left
+                       if ( !$this->valid() ) {
+                               $this->nextShardIteratorIfNotValid();
+                               $continue = $this->valid(); // re-filter unless we ran out of shards
+                       }
+               } while ( $continue );
+       }
+
+       /**
+        * @see Iterator::rewind()
+        * @return void
+        */
+       public function rewind() {
+               $this->pos = 0;
+               $this->curShard = 0;
+               $this->setIteratorFromCurrentShard();
+               do {
+                       $continue = false; // keep scanning shards?
+                       $this->filterViaNext(); // filter out duplicates
+                       // Find the next non-empty shard if no elements are left
+                       if ( !$this->valid() ) {
+                               $this->nextShardIteratorIfNotValid();
+                               $continue = $this->valid(); // re-filter unless we ran out of shards
+                       }
+               } while ( $continue );
+       }
+
+       /**
+        * Filter out duplicate items by advancing to the next ones
+        */
+       protected function filterViaNext() {
+               while ( $this->valid() ) {
+                       $rel = $this->iter->current(); // path relative to given directory
+                       $path = $this->params['dir'] . "/{$rel}"; // full storage path
+                       if ( $this->backend->isSingleShardPathInternal( $path ) ) {
+                               break; // path is only on one shard; no issue with duplicates
+                       } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
+                               // Don't keep listing paths that are on multiple shards
+                               ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
+                       } else {
+                               $this->multiShardPaths[$rel] = 1;
+                               break;
+                       }
+               }
+       }
+
+       /**
+        * If the list iterator for this container shard is out of items,
+        * then move on to the next container that has items.
+        * If there are none, then it advances to the last container.
+        */
+       protected function nextShardIteratorIfNotValid() {
+               while ( !$this->valid() && ++$this->curShard < count( $this->shardSuffixes ) ) {
+                       $this->setIteratorFromCurrentShard();
+               }
+       }
+
+       /**
+        * Set the list iterator to that of the current container shard
+        */
+       protected function setIteratorFromCurrentShard() {
+               $this->iter = $this->listFromShard(
+                       $this->container . $this->shardSuffixes[$this->curShard],
+                       $this->directory, $this->params );
+               // Start loading results so that current() works
+               if ( $this->iter ) {
+                       ( $this->iter instanceof Iterator ) ? $this->iter->rewind() : reset( $this->iter );
+               }
+       }
+
+       /**
+        * Get the list for a given container shard
+        *
+        * @param $container string Resolved container name
+        * @param $dir string Resolved path relative to container
+        * @param $params Array
+        * @return Traversable|Array|null
+        */
+       abstract protected function listFromShard( $container, $dir, array $params );
+}
+
+/**
+ * Iterator for listing directories
+ */
+class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
+       /**
+        * @see FileBackendStoreShardListIterator::listFromShard()
+        * @return Array|null|Traversable
+        */
+       protected function listFromShard( $container, $dir, array $params ) {
+               return $this->backend->getDirectoryListInternal( $container, $dir, $params );
+       }
+}
+
+/**
+ * Iterator for listing regular files
+ */
+class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
+       /**
+        * @see FileBackendStoreShardListIterator::listFromShard()
+        * @return Array|null|Traversable
+        */
+       protected function listFromShard( $container, $dir, array $params ) {
+               return $this->backend->getFileListInternal( $container, $dir, $params );
+       }
+}
diff --git a/includes/filebackend/FileOp.php b/includes/filebackend/FileOp.php
new file mode 100644 (file)
index 0000000..fa87c3a
--- /dev/null
@@ -0,0 +1,778 @@
+<?php
+/**
+ * Helper class for representing operations with transaction support.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * FileBackend helper class for representing operations.
+ * Do not use this class from places outside FileBackend.
+ *
+ * Methods called from FileOpBatch::attempt() should avoid throwing
+ * exceptions at all costs. FileOp objects should be lightweight in order
+ * to support large arrays in memory and serialization.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+abstract class FileOp {
+       /** @var Array */
+       protected $params = array();
+       /** @var FileBackendStore */
+       protected $backend;
+
+       protected $state = self::STATE_NEW; // integer
+       protected $failed = false; // boolean
+       protected $async = false; // boolean
+       protected $useLatest = true; // boolean
+       protected $batchId; // string
+
+       protected $sourceSha1; // string
+       protected $destSameAsSource; // boolean
+
+       /* Object life-cycle */
+       const STATE_NEW = 1;
+       const STATE_CHECKED = 2;
+       const STATE_ATTEMPTED = 3;
+
+       /**
+        * Build a new file operation transaction
+        *
+        * @param $backend FileBackendStore
+        * @param $params Array
+        * @throws MWException
+        */
+       final public function __construct( FileBackendStore $backend, array $params ) {
+               $this->backend = $backend;
+               list( $required, $optional ) = $this->allowedParams();
+               foreach ( $required as $name ) {
+                       if ( isset( $params[$name] ) ) {
+                               $this->params[$name] = $params[$name];
+                       } else {
+                               throw new MWException( "File operation missing parameter '$name'." );
+                       }
+               }
+               foreach ( $optional as $name ) {
+                       if ( isset( $params[$name] ) ) {
+                               $this->params[$name] = $params[$name];
+                       }
+               }
+               $this->params = $params;
+       }
+
+       /**
+        * Set the batch UUID this operation belongs to
+        *
+        * @param $batchId string
+        * @return void
+        */
+       final public function setBatchId( $batchId ) {
+               $this->batchId = $batchId;
+       }
+
+       /**
+        * Whether to allow stale data for file reads and stat checks
+        *
+        * @param $allowStale bool
+        * @return void
+        */
+       final public function allowStaleReads( $allowStale ) {
+               $this->useLatest = !$allowStale;
+       }
+
+       /**
+        * Get the value of the parameter with the given name
+        *
+        * @param $name string
+        * @return mixed Returns null if the parameter is not set
+        */
+       final public function getParam( $name ) {
+               return isset( $this->params[$name] ) ? $this->params[$name] : null;
+       }
+
+       /**
+        * Check if this operation failed precheck() or attempt()
+        *
+        * @return bool
+        */
+       final public function failed() {
+               return $this->failed;
+       }
+
+       /**
+        * Get a new empty predicates array for precheck()
+        *
+        * @return Array
+        */
+       final public static function newPredicates() {
+               return array( 'exists' => array(), 'sha1' => array() );
+       }
+
+       /**
+        * Get a new empty dependency tracking array for paths read/written to
+        *
+        * @return Array
+        */
+       final public static function newDependencies() {
+               return array( 'read' => array(), 'write' => array() );
+       }
+
+       /**
+        * Update a dependency tracking array to account for this operation
+        *
+        * @param $deps Array Prior path reads/writes; format of FileOp::newPredicates()
+        * @return Array
+        */
+       final public function applyDependencies( array $deps ) {
+               $deps['read']  += array_fill_keys( $this->storagePathsRead(), 1 );
+               $deps['write'] += array_fill_keys( $this->storagePathsChanged(), 1 );
+               return $deps;
+       }
+
+       /**
+        * Check if this operation changes files listed in $paths
+        *
+        * @param $paths Array Prior path reads/writes; format of FileOp::newPredicates()
+        * @return boolean
+        */
+       final public function dependsOn( array $deps ) {
+               foreach ( $this->storagePathsChanged() as $path ) {
+                       if ( isset( $deps['read'][$path] ) || isset( $deps['write'][$path] ) ) {
+                               return true; // "output" or "anti" dependency
+                       }
+               }
+               foreach ( $this->storagePathsRead() as $path ) {
+                       if ( isset( $deps['write'][$path] ) ) {
+                               return true; // "flow" dependency
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Get the file journal entries for this file operation
+        *
+        * @param $oPredicates Array Pre-op info about files (format of FileOp::newPredicates)
+        * @param $nPredicates Array Post-op info about files (format of FileOp::newPredicates)
+        * @return Array
+        */
+       final public function getJournalEntries( array $oPredicates, array $nPredicates ) {
+               $nullEntries = array();
+               $updateEntries = array();
+               $deleteEntries = array();
+               $pathsUsed = array_merge( $this->storagePathsRead(), $this->storagePathsChanged() );
+               foreach ( $pathsUsed as $path ) {
+                       $nullEntries[] = array( // assertion for recovery
+                               'op'      => 'null',
+                               'path'    => $path,
+                               'newSha1' => $this->fileSha1( $path, $oPredicates )
+                       );
+               }
+               foreach ( $this->storagePathsChanged() as $path ) {
+                       if ( $nPredicates['sha1'][$path] === false ) { // deleted
+                               $deleteEntries[] = array(
+                                       'op'      => 'delete',
+                                       'path'    => $path,
+                                       'newSha1' => ''
+                               );
+                       } else { // created/updated
+                               $updateEntries[] = array(
+                                       'op'      => $this->fileExists( $path, $oPredicates ) ? 'update' : 'create',
+                                       'path'    => $path,
+                                       'newSha1' => $nPredicates['sha1'][$path]
+                               );
+                       }
+               }
+               return array_merge( $nullEntries, $updateEntries, $deleteEntries );
+       }
+
+       /**
+        * Check preconditions of the operation without writing anything
+        *
+        * @param $predicates Array
+        * @return Status
+        */
+       final public function precheck( array &$predicates ) {
+               if ( $this->state !== self::STATE_NEW ) {
+                       return Status::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state );
+               }
+               $this->state = self::STATE_CHECKED;
+               $status = $this->doPrecheck( $predicates );
+               if ( !$status->isOK() ) {
+                       $this->failed = true;
+               }
+               return $status;
+       }
+
+       /**
+        * @return Status
+        */
+       protected function doPrecheck( array &$predicates ) {
+               return Status::newGood();
+       }
+
+       /**
+        * Attempt the operation
+        *
+        * @return Status
+        */
+       final public function attempt() {
+               if ( $this->state !== self::STATE_CHECKED ) {
+                       return Status::newFatal( 'fileop-fail-state', self::STATE_CHECKED, $this->state );
+               } elseif ( $this->failed ) { // failed precheck
+                       return Status::newFatal( 'fileop-fail-attempt-precheck' );
+               }
+               $this->state = self::STATE_ATTEMPTED;
+               $status = $this->doAttempt();
+               if ( !$status->isOK() ) {
+                       $this->failed = true;
+                       $this->logFailure( 'attempt' );
+               }
+               return $status;
+       }
+
+       /**
+        * @return Status
+        */
+       protected function doAttempt() {
+               return Status::newGood();
+       }
+
+       /**
+        * Attempt the operation in the background
+        *
+        * @return Status
+        */
+       final public function attemptAsync() {
+               $this->async = true;
+               $result = $this->attempt();
+               $this->async = false;
+               return $result;
+       }
+
+       /**
+        * Get the file operation parameters
+        *
+        * @return Array (required params list, optional params list)
+        */
+       protected function allowedParams() {
+               return array( array(), array() );
+       }
+
+       /**
+        * Adjust params to FileBackendStore internal file calls
+        *
+        * @param $params Array
+        * @return Array (required params list, optional params list)
+        */
+       protected function setFlags( array $params ) {
+               return array( 'async' => $this->async ) + $params;
+       }
+
+       /**
+        * Get a list of storage paths read from for this operation
+        *
+        * @return Array
+        */
+       final public function storagePathsRead() {
+               return array_map( 'FileBackend::normalizeStoragePath', $this->doStoragePathsRead() );
+       }
+
+       /**
+        * @see FileOp::storagePathsRead()
+        * @return Array
+        */
+       protected function doStoragePathsRead() {
+               return array();
+       }
+
+       /**
+        * Get a list of storage paths written to for this operation
+        *
+        * @return Array
+        */
+       final public function storagePathsChanged() {
+               return array_map( 'FileBackend::normalizeStoragePath', $this->doStoragePathsChanged() );
+       }
+
+       /**
+        * @see FileOp::storagePathsChanged()
+        * @return Array
+        */
+       protected function doStoragePathsChanged() {
+               return array();
+       }
+
+       /**
+        * Check for errors with regards to the destination file already existing.
+        * This also updates the destSameAsSource and sourceSha1 member variables.
+        * A bad status will be returned if there is no chance it can be overwritten.
+        *
+        * @param $predicates Array
+        * @return Status
+        */
+       protected function precheckDestExistence( array $predicates ) {
+               $status = Status::newGood();
+               // Get hash of source file/string and the destination file
+               $this->sourceSha1 = $this->getSourceSha1Base36(); // FS file or data string
+               if ( $this->sourceSha1 === null ) { // file in storage?
+                       $this->sourceSha1 = $this->fileSha1( $this->params['src'], $predicates );
+               }
+               $this->destSameAsSource = false;
+               if ( $this->fileExists( $this->params['dst'], $predicates ) ) {
+                       if ( $this->getParam( 'overwrite' ) ) {
+                               return $status; // OK
+                       } elseif ( $this->getParam( 'overwriteSame' ) ) {
+                               $dhash = $this->fileSha1( $this->params['dst'], $predicates );
+                               // Check if hashes are valid and match each other...
+                               if ( !strlen( $this->sourceSha1 ) || !strlen( $dhash ) ) {
+                                       $status->fatal( 'backend-fail-hashes' );
+                               } elseif ( $this->sourceSha1 !== $dhash ) {
+                                       // Give an error if the files are not identical
+                                       $status->fatal( 'backend-fail-notsame', $this->params['dst'] );
+                               } else {
+                                       $this->destSameAsSource = true; // OK
+                               }
+                               return $status; // do nothing; either OK or bad status
+                       } else {
+                               $status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
+                               return $status;
+                       }
+               }
+               return $status;
+       }
+
+       /**
+        * precheckDestExistence() helper function to get the source file SHA-1.
+        * Subclasses should overwride this iff the source is not in storage.
+        *
+        * @return string|bool Returns false on failure
+        */
+       protected function getSourceSha1Base36() {
+               return null; // N/A
+       }
+
+       /**
+        * Check if a file will exist in storage when this operation is attempted
+        *
+        * @param $source string Storage path
+        * @param $predicates Array
+        * @return bool
+        */
+       final protected function fileExists( $source, array $predicates ) {
+               if ( isset( $predicates['exists'][$source] ) ) {
+                       return $predicates['exists'][$source]; // previous op assures this
+               } else {
+                       $params = array( 'src' => $source, 'latest' => $this->useLatest );
+                       return $this->backend->fileExists( $params );
+               }
+       }
+
+       /**
+        * Get the SHA-1 of a file in storage when this operation is attempted
+        *
+        * @param $source string Storage path
+        * @param $predicates Array
+        * @return string|bool False on failure
+        */
+       final protected function fileSha1( $source, array $predicates ) {
+               if ( isset( $predicates['sha1'][$source] ) ) {
+                       return $predicates['sha1'][$source]; // previous op assures this
+               } else {
+                       $params = array( 'src' => $source, 'latest' => $this->useLatest );
+                       return $this->backend->getFileSha1Base36( $params );
+               }
+       }
+
+       /**
+        * Get the backend this operation is for
+        *
+        * @return FileBackendStore
+        */
+       public function getBackend() {
+               return $this->backend;
+       }
+
+       /**
+        * Log a file operation failure and preserve any temp files
+        *
+        * @param $action string
+        * @return void
+        */
+       final public function logFailure( $action ) {
+               $params = $this->params;
+               $params['failedAction'] = $action;
+               try {
+                       wfDebugLog( 'FileOperation', get_class( $this ) .
+                               " failed (batch #{$this->batchId}): " . FormatJson::encode( $params ) );
+               } catch ( Exception $e ) {
+                       // bad config? debug log error?
+               }
+       }
+}
+
+/**
+ * Store a file into the backend from a file on the file system.
+ * Parameters similar to FileBackendStore::storeInternal(), which include:
+ *   - src           : source path on file system
+ *   - dst           : destination storage path
+ *   - overwrite     : do nothing and pass if an identical file exists at destination
+ *   - overwriteSame : override any existing file at destination
+ */
+class StoreFileOp extends FileOp {
+       /**
+        * @return array
+        */
+       protected function allowedParams() {
+               return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+       }
+
+       /**
+        * @param $predicates array
+        * @return Status
+        */
+       protected function doPrecheck( array &$predicates ) {
+               $status = Status::newGood();
+               // Check if the source file exists on the file system
+               if ( !is_file( $this->params['src'] ) ) {
+                       $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+                       return $status;
+               // Check if the source file is too big
+               } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
+                       $status->fatal( 'backend-fail-maxsize',
+                               $this->params['dst'], $this->backend->maxFileSizeInternal() );
+                       $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+                       return $status;
+               // Check if a file can be placed at the destination
+               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
+                       $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+                       return $status;
+               }
+               // Check if destination file exists
+               $status->merge( $this->precheckDestExistence( $predicates ) );
+               if ( $status->isOK() ) {
+                       // Update file existence predicates
+                       $predicates['exists'][$this->params['dst']] = true;
+                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+               }
+               return $status; // safe to call attempt()
+       }
+
+       /**
+        * @return Status
+        */
+       protected function doAttempt() {
+               // Store the file at the destination
+               if ( !$this->destSameAsSource ) {
+                       return $this->backend->storeInternal( $this->setFlags( $this->params ) );
+               }
+               return Status::newGood();
+       }
+
+       /**
+        * @return bool|string
+        */
+       protected function getSourceSha1Base36() {
+               wfSuppressWarnings();
+               $hash = sha1_file( $this->params['src'] );
+               wfRestoreWarnings();
+               if ( $hash !== false ) {
+                       $hash = wfBaseConvert( $hash, 16, 36, 31 );
+               }
+               return $hash;
+       }
+
+       protected function doStoragePathsChanged() {
+               return array( $this->params['dst'] );
+       }
+}
+
+/**
+ * Create a file in the backend with the given content.
+ * Parameters similar to FileBackendStore::createInternal(), which include:
+ *   - content       : the raw file contents
+ *   - dst           : destination storage path
+ *   - overwrite     : do nothing and pass if an identical file exists at destination
+ *   - overwriteSame : override any existing file at destination
+ */
+class CreateFileOp extends FileOp {
+       protected function allowedParams() {
+               return array( array( 'content', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+       }
+
+       protected function doPrecheck( array &$predicates ) {
+               $status = Status::newGood();
+               // Check if the source data is too big
+               if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
+                       $status->fatal( 'backend-fail-maxsize',
+                               $this->params['dst'], $this->backend->maxFileSizeInternal() );
+                       $status->fatal( 'backend-fail-create', $this->params['dst'] );
+                       return $status;
+               // Check if a file can be placed at the destination
+               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
+                       $status->fatal( 'backend-fail-create', $this->params['dst'] );
+                       return $status;
+               }
+               // Check if destination file exists
+               $status->merge( $this->precheckDestExistence( $predicates ) );
+               if ( $status->isOK() ) {
+                       // Update file existence predicates
+                       $predicates['exists'][$this->params['dst']] = true;
+                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+               }
+               return $status; // safe to call attempt()
+       }
+
+       /**
+        * @return Status
+        */
+       protected function doAttempt() {
+               if ( !$this->destSameAsSource ) {
+                       // Create the file at the destination
+                       return $this->backend->createInternal( $this->setFlags( $this->params ) );
+               }
+               return Status::newGood();
+       }
+
+       /**
+        * @return bool|String
+        */
+       protected function getSourceSha1Base36() {
+               return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
+       }
+
+       /**
+        * @return array
+        */
+       protected function doStoragePathsChanged() {
+               return array( $this->params['dst'] );
+       }
+}
+
+/**
+ * Copy a file from one storage path to another in the backend.
+ * Parameters similar to FileBackendStore::copyInternal(), which include:
+ *   - src           : source storage path
+ *   - dst           : destination storage path
+ *   - overwrite     : do nothing and pass if an identical file exists at destination
+ *   - overwriteSame : override any existing file at destination
+ */
+class CopyFileOp extends FileOp {
+       /**
+        * @return array
+        */
+       protected function allowedParams() {
+               return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+       }
+
+       /**
+        * @param $predicates array
+        * @return Status
+        */
+       protected function doPrecheck( array &$predicates ) {
+               $status = Status::newGood();
+               // Check if the source file exists
+               if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+                       $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+                       return $status;
+               // Check if a file can be placed at the destination
+               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
+                       $status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
+                       return $status;
+               }
+               // Check if destination file exists
+               $status->merge( $this->precheckDestExistence( $predicates ) );
+               if ( $status->isOK() ) {
+                       // Update file existence predicates
+                       $predicates['exists'][$this->params['dst']] = true;
+                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+               }
+               return $status; // safe to call attempt()
+       }
+
+       /**
+        * @return Status
+        */
+       protected function doAttempt() {
+               // Do nothing if the src/dst paths are the same
+               if ( $this->params['src'] !== $this->params['dst'] ) {
+                       // Copy the file into the destination
+                       if ( !$this->destSameAsSource ) {
+                               return $this->backend->copyInternal( $this->setFlags( $this->params ) );
+                       }
+               }
+               return Status::newGood();
+       }
+
+       /**
+        * @return array
+        */
+       protected function doStoragePathsRead() {
+               return array( $this->params['src'] );
+       }
+
+       /**
+        * @return array
+        */
+       protected function doStoragePathsChanged() {
+               return array( $this->params['dst'] );
+       }
+}
+
+/**
+ * Move a file from one storage path to another in the backend.
+ * Parameters similar to FileBackendStore::moveInternal(), which include:
+ *   - src           : source storage path
+ *   - dst           : destination storage path
+ *   - overwrite     : do nothing and pass if an identical file exists at destination
+ *   - overwriteSame : override any existing file at destination
+ */
+class MoveFileOp extends FileOp {
+       /**
+        * @return array
+        */
+       protected function allowedParams() {
+               return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+       }
+
+       /**
+        * @param $predicates array
+        * @return Status
+        */
+       protected function doPrecheck( array &$predicates ) {
+               $status = Status::newGood();
+               // Check if the source file exists
+               if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+                       $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+                       return $status;
+               // Check if a file can be placed at the destination
+               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
+                       $status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
+                       return $status;
+               }
+               // Check if destination file exists
+               $status->merge( $this->precheckDestExistence( $predicates ) );
+               if ( $status->isOK() ) {
+                       // Update file existence predicates
+                       $predicates['exists'][$this->params['src']] = false;
+                       $predicates['sha1'][$this->params['src']] = false;
+                       $predicates['exists'][$this->params['dst']] = true;
+                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+               }
+               return $status; // safe to call attempt()
+       }
+
+       /**
+        * @return Status
+        */
+       protected function doAttempt() {
+               // Do nothing if the src/dst paths are the same
+               if ( $this->params['src'] !== $this->params['dst'] ) {
+                       if ( !$this->destSameAsSource ) {
+                               // Move the file into the destination
+                               return $this->backend->moveInternal( $this->setFlags( $this->params ) );
+                       } else {
+                               // Just delete source as the destination needs no changes
+                               $params = array( 'src' => $this->params['src'] );
+                               return $this->backend->deleteInternal( $this->setFlags( $params ) );
+                       }
+               }
+               return Status::newGood();
+       }
+
+       /**
+        * @return array
+        */
+       protected function doStoragePathsRead() {
+               return array( $this->params['src'] );
+       }
+
+       /**
+        * @return array
+        */
+       protected function doStoragePathsChanged() {
+               return array( $this->params['src'], $this->params['dst'] );
+       }
+}
+
+/**
+ * Delete a file at the given storage path from the backend.
+ * Parameters similar to FileBackendStore::deleteInternal(), which include:
+ *   - src                 : source storage path
+ *   - ignoreMissingSource : don't return an error if the file does not exist
+ */
+class DeleteFileOp extends FileOp {
+       /**
+        * @return array
+        */
+       protected function allowedParams() {
+               return array( array( 'src' ), array( 'ignoreMissingSource' ) );
+       }
+
+       protected $needsDelete = true;
+
+       /**
+        * @param array $predicates
+        * @return Status
+        */
+       protected function doPrecheck( array &$predicates ) {
+               $status = Status::newGood();
+               // Check if the source file exists
+               if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+                       if ( !$this->getParam( 'ignoreMissingSource' ) ) {
+                               $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+                               return $status;
+                       }
+                       $this->needsDelete = false;
+               }
+               // Update file existence predicates
+               $predicates['exists'][$this->params['src']] = false;
+               $predicates['sha1'][$this->params['src']] = false;
+               return $status; // safe to call attempt()
+       }
+
+       /**
+        * @return Status
+        */
+       protected function doAttempt() {
+               if ( $this->needsDelete ) {
+                       // Delete the source file
+                       return $this->backend->deleteInternal( $this->setFlags( $this->params ) );
+               }
+               return Status::newGood();
+       }
+
+       /**
+        * @return array
+        */
+       protected function doStoragePathsChanged() {
+               return array( $this->params['src'] );
+       }
+}
+
+/**
+ * Placeholder operation that has no params and does nothing
+ */
+class NullFileOp extends FileOp {}
diff --git a/includes/filebackend/FileOpBatch.php b/includes/filebackend/FileOpBatch.php
new file mode 100644 (file)
index 0000000..3355872
--- /dev/null
@@ -0,0 +1,240 @@
+<?php
+/**
+ * Helper class for representing batch file operations.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * Helper class for representing batch file operations.
+ * Do not use this class from places outside FileBackend.
+ *
+ * Methods should avoid throwing exceptions at all costs.
+ *
+ * @ingroup FileBackend
+ * @since 1.20
+ */
+class FileOpBatch {
+       /* Timeout related parameters */
+       const MAX_BATCH_SIZE = 1000; // integer
+
+       /**
+        * Attempt to perform a series of file operations.
+        * Callers are responsible for handling file locking.
+        *
+        * $opts is an array of options, including:
+        *   - force        : Errors that would normally cause a rollback do not.
+        *                    The remaining operations are still attempted if any fail.
+        *   - allowStale   : Don't require the latest available data.
+        *                    This can increase performance for non-critical writes.
+        *                    This has no effect unless the 'force' flag is set.
+        *   - nonJournaled : Don't log this operation batch in the file journal.
+        *   - concurrency  : Try to do this many operations in parallel when possible.
+        *
+        * The resulting Status will be "OK" unless:
+        *   - a) unexpected operation errors occurred (network partitions, disk full...)
+        *   - b) significant operation errors occurred and 'force' was not set
+        *
+        * @param $performOps Array List of FileOp operations
+        * @param $opts Array Batch operation options
+        * @param $journal FileJournal Journal to log operations to
+        * @return Status
+        */
+       public static function attempt( array $performOps, array $opts, FileJournal $journal ) {
+               wfProfileIn( __METHOD__ );
+               $status = Status::newGood();
+
+               $n = count( $performOps );
+               if ( $n > self::MAX_BATCH_SIZE ) {
+                       $status->fatal( 'backend-fail-batchsize', $n, self::MAX_BATCH_SIZE );
+                       wfProfileOut( __METHOD__ );
+                       return $status;
+               }
+
+               $batchId = $journal->getTimestampedUUID();
+               $allowStale = !empty( $opts['allowStale'] );
+               $ignoreErrors = !empty( $opts['force'] );
+               $journaled = empty( $opts['nonJournaled'] );
+               $maxConcurrency = isset( $opts['concurrency'] ) ? $opts['concurrency'] : 1;
+
+               $entries = array(); // file journal entry list
+               $predicates = FileOp::newPredicates(); // account for previous ops in prechecks
+               $curBatch = array(); // concurrent FileOp sub-batch accumulation
+               $curBatchDeps = FileOp::newDependencies(); // paths used in FileOp sub-batch
+               $pPerformOps = array(); // ordered list of concurrent FileOp sub-batches
+               $lastBackend = null; // last op backend name
+               // Do pre-checks for each operation; abort on failure...
+               foreach ( $performOps as $index => $fileOp ) {
+                       $backendName = $fileOp->getBackend()->getName();
+                       $fileOp->setBatchId( $batchId ); // transaction ID
+                       $fileOp->allowStaleReads( $allowStale ); // consistency level
+                       // Decide if this op can be done concurrently within this sub-batch
+                       // or if a new concurrent sub-batch must be started after this one...
+                       if ( $fileOp->dependsOn( $curBatchDeps )
+                               || count( $curBatch ) >= $maxConcurrency
+                               || ( $backendName !== $lastBackend && count( $curBatch ) )
+                       ) {
+                               $pPerformOps[] = $curBatch; // push this batch
+                               $curBatch = array(); // start a new sub-batch
+                               $curBatchDeps = FileOp::newDependencies();
+                       }
+                       $lastBackend = $backendName;
+                       $curBatch[$index] = $fileOp; // keep index
+                       // Update list of affected paths in this batch
+                       $curBatchDeps = $fileOp->applyDependencies( $curBatchDeps );
+                       // Simulate performing the operation...
+                       $oldPredicates = $predicates;
+                       $subStatus = $fileOp->precheck( $predicates ); // updates $predicates
+                       $status->merge( $subStatus );
+                       if ( $subStatus->isOK() ) {
+                               if ( $journaled ) { // journal log entries
+                                       $entries = array_merge( $entries,
+                                               $fileOp->getJournalEntries( $oldPredicates, $predicates ) );
+                               }
+                       } else { // operation failed?
+                               $status->success[$index] = false;
+                               ++$status->failCount;
+                               if ( !$ignoreErrors ) {
+                                       wfProfileOut( __METHOD__ );
+                                       return $status; // abort
+                               }
+                       }
+               }
+               // Push the last sub-batch
+               if ( count( $curBatch ) ) {
+                       $pPerformOps[] = $curBatch;
+               }
+
+               // Log the operations in the file journal...
+               if ( count( $entries ) ) {
+                       $subStatus = $journal->logChangeBatch( $entries, $batchId );
+                       if ( !$subStatus->isOK() ) {
+                               wfProfileOut( __METHOD__ );
+                               return $subStatus; // abort
+                       }
+               }
+
+               if ( $ignoreErrors ) { // treat precheck() fatals as mere warnings
+                       $status->setResult( true, $status->value );
+               }
+
+               // Attempt each operation (in parallel if allowed and possible)...
+               if ( count( $pPerformOps ) < count( $performOps ) ) {
+                       self::runBatchParallel( $pPerformOps, $status );
+               } else {
+                       self::runBatchSeries( $performOps, $status );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * Attempt a list of file operations in series.
+        * This will abort remaining ops on failure.
+        *
+        * @param $performOps Array
+        * @param $status Status
+        * @return bool Success
+        */
+       protected static function runBatchSeries( array $performOps, Status $status ) {
+               foreach ( $performOps as $index => $fileOp ) {
+                       if ( $fileOp->failed() ) {
+                               continue; // nothing to do
+                       }
+                       $subStatus = $fileOp->attempt();
+                       $status->merge( $subStatus );
+                       if ( $subStatus->isOK() ) {
+                               $status->success[$index] = true;
+                               ++$status->successCount;
+                       } else {
+                               $status->success[$index] = false;
+                               ++$status->failCount;
+                               // We can't continue (even with $ignoreErrors) as $predicates is wrong.
+                               // Log the remaining ops as failed for recovery...
+                               for ( $i = ($index + 1); $i < count( $performOps ); $i++ ) {
+                                       $performOps[$i]->logFailure( 'attempt_aborted' );
+                               }
+                               return false; // bail out
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * Attempt a list of file operations sub-batches in series.
+        *
+        * The operations *in* each sub-batch will be done in parallel.
+        * The caller is responsible for making sure the operations
+        * within any given sub-batch do not depend on each other.
+        * This will abort remaining ops on failure.
+        *
+        * @param $pPerformOps Array
+        * @param $status Status
+        * @return bool Success
+        */
+       protected static function runBatchParallel( array $pPerformOps, Status $status ) {
+               $aborted = false;
+               foreach ( $pPerformOps as $performOpsBatch ) {
+                       if ( $aborted ) { // check batch op abort flag...
+                               // We can't continue (even with $ignoreErrors) as $predicates is wrong.
+                               // Log the remaining ops as failed for recovery...
+                               foreach ( $performOpsBatch as $i => $fileOp ) {
+                                       $performOpsBatch[$i]->logFailure( 'attempt_aborted' );
+                               }
+                               continue;
+                       }
+                       $statuses = array();
+                       $opHandles = array();
+                       // Get the backend; all sub-batch ops belong to a single backend
+                       $backend = reset( $performOpsBatch )->getBackend();
+                       // If attemptAsync() returns synchronously, it was either an
+                       // error Status or the backend just doesn't support async ops.
+                       foreach ( $performOpsBatch as $i => $fileOp ) {
+                               if ( !$fileOp->failed() ) { // failed => already has Status
+                                       $subStatus = $fileOp->attemptAsync();
+                                       if ( $subStatus->value instanceof FileBackendStoreOpHandle ) {
+                                               $opHandles[$i] = $subStatus->value; // deferred
+                                       } else {
+                                               $statuses[$i] = $subStatus; // done already
+                                       }
+                               }
+                       }
+                       // Try to do all the operations concurrently...
+                       $statuses = $statuses + $backend->executeOpHandlesInternal( $opHandles );
+                       // Marshall and merge all the responses (blocking)...
+                       foreach ( $performOpsBatch as $i => $fileOp ) {
+                               if ( !$fileOp->failed() ) { // failed => already has Status
+                                       $subStatus = $statuses[$i];
+                                       $status->merge( $subStatus );
+                                       if ( $subStatus->isOK() ) {
+                                               $status->success[$i] = true;
+                                               ++$status->successCount;
+                                       } else {
+                                               $status->success[$i] = false;
+                                               ++$status->failCount;
+                                               $aborted = true; // set abort flag; we can't continue
+                                       }
+                               }
+                       }
+               }
+               return $status;
+       }
+}
diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php
new file mode 100644 (file)
index 0000000..5fe568a
--- /dev/null
@@ -0,0 +1,1432 @@
+<?php
+/**
+ * OpenStack Swift based file backend.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ * @author Russ Nelson
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Class for an OpenStack Swift based file backend.
+ *
+ * This requires the SwiftCloudFiles MediaWiki extension, which includes
+ * the php-cloudfiles library (https://github.com/rackspace/php-cloudfiles).
+ * php-cloudfiles requires the curl, fileinfo, and mb_string PHP extensions.
+ *
+ * Status messages should avoid mentioning the Swift account name.
+ * Likewise, error suppression should be used to avoid path disclosure.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class SwiftFileBackend extends FileBackendStore {
+       /** @var CF_Authentication */
+       protected $auth; // Swift authentication handler
+       protected $authTTL; // integer seconds
+       protected $swiftAnonUser; // string; username to handle unauthenticated requests
+       protected $swiftUseCDN; // boolean; whether CloudFiles CDN is enabled
+       protected $swiftCDNExpiry; // integer; how long to cache things in the CDN
+       protected $swiftCDNPurgable; // boolean; whether object CDN purging is enabled
+
+       protected $maxContCacheSize = 300; // integer; max containers with entries
+
+       /** @var CF_Connection */
+       protected $conn; // Swift connection handle
+       protected $connStarted = 0; // integer UNIX timestamp
+       protected $connContainers = array(); // container object cache
+       protected $connException; // CloudFiles exception
+
+       /**
+        * @see FileBackendStore::__construct()
+        * Additional $config params include:
+        *   - swiftAuthUrl       : Swift authentication server URL
+        *   - swiftUser          : Swift user used by MediaWiki (account:username)
+        *   - swiftKey           : Swift authentication key for the above user
+        *   - swiftAuthTTL       : Swift authentication TTL (seconds)
+        *   - swiftAnonUser      : Swift user used for end-user requests (account:username).
+        *                          If set, then views of public containers are assumed to go
+        *                          through this user. If not set, then public containers are
+        *                          accessible to unauthenticated requests via ".r:*" in the ACL.
+        *   - swiftUseCDN        : Whether a Cloud Files Content Delivery Network is set up
+        *   - swiftCDNExpiry     : How long (in seconds) to store content in the CDN.
+        *                          If files may likely change, this should probably not exceed
+        *                          a few days. For example, deletions may take this long to apply.
+        *                          If object purging is enabled, however, this is not an issue.
+        *   - swiftCDNPurgable   : Whether object purge requests are allowed by the CDN.
+        *   - shardViaHashLevels : Map of container names to sharding config with:
+        *                             - base   : base of hash characters, 16 or 36
+        *                             - levels : the number of hash levels (and digits)
+        *                             - repeat : hash subdirectories are prefixed with all the
+        *                                        parent hash directory names (e.g. "a/ab/abc")
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+               if ( !MWInit::classExists( 'CF_Constants' ) ) {
+                       throw new MWException( 'SwiftCloudFiles extension not installed.' );
+               }
+               // Required settings
+               $this->auth = new CF_Authentication(
+                       $config['swiftUser'],
+                       $config['swiftKey'],
+                       null, // account; unused
+                       $config['swiftAuthUrl']
+               );
+               // Optional settings
+               $this->authTTL = isset( $config['swiftAuthTTL'] )
+                       ? $config['swiftAuthTTL']
+                       : 5 * 60; // some sane number
+               $this->swiftAnonUser = isset( $config['swiftAnonUser'] )
+                       ? $config['swiftAnonUser']
+                       : '';
+               $this->shardViaHashLevels = isset( $config['shardViaHashLevels'] )
+                       ? $config['shardViaHashLevels']
+                       : '';
+               $this->swiftUseCDN = isset( $config['swiftUseCDN'] )
+                       ? $config['swiftUseCDN']
+                       : false;
+               $this->swiftCDNExpiry = isset( $config['swiftCDNExpiry'] )
+                       ? $config['swiftCDNExpiry']
+                       : 3600; // hour
+               $this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] )
+                       ? $config['swiftCDNPurgable']
+                       : true;
+               // Cache container info to mask latency
+               $this->memCache = wfGetMainCache();
+       }
+
+       /**
+        * @see FileBackendStore::resolveContainerPath()
+        * @return null
+        */
+       protected function resolveContainerPath( $container, $relStoragePath ) {
+               if ( !mb_check_encoding( $relStoragePath, 'UTF-8' ) ) { // mb_string required by CF
+                       return null; // not UTF-8, makes it hard to use CF and the swift HTTP API
+               } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
+                       return null; // too long for Swift
+               }
+               return $relStoragePath;
+       }
+
+       /**
+        * @see FileBackendStore::isPathUsableInternal()
+        * @return bool
+        */
+       public function isPathUsableInternal( $storagePath ) {
+               list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath );
+               if ( $rel === null ) {
+                       return false; // invalid
+               }
+
+               try {
+                       $this->getContainer( $container );
+                       return true; // container exists
+               } catch ( NoSuchContainerException $e ) {
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, null, __METHOD__, array( 'path' => $storagePath ) );
+               }
+
+               return false;
+       }
+
+       /**
+        * @see FileBackendStore::doCreateInternal()
+        * @return Status
+        */
+       protected function doCreateInternal( array $params ) {
+               $status = Status::newGood();
+
+               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
+               if ( $dstRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               // (a) Check the destination container and object
+               try {
+                       $dContObj = $this->getContainer( $dstCont );
+                       if ( empty( $params['overwrite'] ) &&
+                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
+                       {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               } catch ( NoSuchContainerException $e ) {
+                       $status->fatal( 'backend-fail-create', $params['dst'] );
+                       return $status;
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               // (b) Get a SHA-1 hash of the object
+               $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 );
+
+               // (c) Actually create the object
+               try {
+                       // Create a fresh CF_Object with no fields preloaded.
+                       // We don't want to preserve headers, metadata, and such.
+                       $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
+                       // Note: metadata keys stored as [Upper case char][[Lower case char]...]
+                       $obj->metadata = array( 'Sha1base36' => $sha1Hash );
+                       // Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59).
+                       // The MD5 here will be checked within Swift against its own MD5.
+                       $obj->set_etag( md5( $params['content'] ) );
+                       // Use the same content type as StreamFile for security
+                       $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
+                       if ( !strlen( $obj->content_type ) ) { // special case
+                               $obj->content_type = 'unknown/unknown';
+                       }
+                       if ( !empty( $params['async'] ) ) { // deferred
+                               $handle = $obj->write_async( $params['content'] );
+                               $status->value = new SwiftFileOpHandle( $this, $params, 'Create', $handle );
+                               $status->value->affectedObjects[] = $obj;
+                       } else { // actually write the object in Swift
+                               $obj->write( $params['content'] );
+                               $this->purgeCDNCache( array( $obj ) );
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( BadContentTypeException $e ) {
+                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseCreate( CF_Async_Op $cfOp, Status $status, array $params ) {
+               try {
+                       $cfOp->getLastResponse();
+               } catch ( BadContentTypeException $e ) {
+                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doStoreInternal()
+        * @return Status
+        */
+       protected function doStoreInternal( array $params ) {
+               $status = Status::newGood();
+
+               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
+               if ( $dstRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               // (a) Check the destination container and object
+               try {
+                       $dContObj = $this->getContainer( $dstCont );
+                       if ( empty( $params['overwrite'] ) &&
+                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
+                       {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               } catch ( NoSuchContainerException $e ) {
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+                       return $status;
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               // (b) Get a SHA-1 hash of the object
+               $sha1Hash = sha1_file( $params['src'] );
+               if ( $sha1Hash === false ) { // source doesn't exist?
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+                       return $status;
+               }
+               $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 );
+
+               // (c) Actually store the object
+               try {
+                       // Create a fresh CF_Object with no fields preloaded.
+                       // We don't want to preserve headers, metadata, and such.
+                       $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
+                       // Note: metadata keys stored as [Upper case char][[Lower case char]...]
+                       $obj->metadata = array( 'Sha1base36' => $sha1Hash );
+                       // The MD5 here will be checked within Swift against its own MD5.
+                       $obj->set_etag( md5_file( $params['src'] ) );
+                       // Use the same content type as StreamFile for security
+                       $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
+                       if ( !strlen( $obj->content_type ) ) { // special case
+                               $obj->content_type = 'unknown/unknown';
+                       }
+                       if ( !empty( $params['async'] ) ) { // deferred
+                               wfSuppressWarnings();
+                               $fp = fopen( $params['src'], 'rb' );
+                               wfRestoreWarnings();
+                               if ( !$fp ) {
+                                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+                               } else {
+                                       $handle = $obj->write_async( $fp, filesize( $params['src'] ), true );
+                                       $status->value = new SwiftFileOpHandle( $this, $params, 'Store', $handle );
+                                       $status->value->resourcesToClose[] = $fp;
+                                       $status->value->affectedObjects[] = $obj;
+                               }
+                       } else { // actually write the object in Swift
+                               $obj->load_from_filename( $params['src'], true ); // calls $obj->write()
+                               $this->purgeCDNCache( array( $obj ) );
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( BadContentTypeException $e ) {
+                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+               } catch ( IOException $e ) {
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseStore( CF_Async_Op $cfOp, Status $status, array $params ) {
+               try {
+                       $cfOp->getLastResponse();
+               } catch ( BadContentTypeException $e ) {
+                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+               } catch ( IOException $e ) {
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doCopyInternal()
+        * @return Status
+        */
+       protected function doCopyInternal( array $params ) {
+               $status = Status::newGood();
+
+               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+               if ( $srcRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+                       return $status;
+               }
+
+               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
+               if ( $dstRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               // (a) Check the source/destination containers and destination object
+               try {
+                       $sContObj = $this->getContainer( $srcCont );
+                       $dContObj = $this->getContainer( $dstCont );
+                       if ( empty( $params['overwrite'] ) &&
+                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
+                       {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               } catch ( NoSuchContainerException $e ) {
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+                       return $status;
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               // (b) Actually copy the file to the destination
+               try {
+                       $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
+                       if ( !empty( $params['async'] ) ) { // deferred
+                               $handle = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel );
+                               $status->value = new SwiftFileOpHandle( $this, $params, 'Copy', $handle );
+                               $status->value->affectedObjects[] = $dstObj;
+                       } else { // actually write the object in Swift
+                               $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel );
+                               $this->purgeCDNCache( array( $dstObj ) );
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( NoSuchObjectException $e ) { // source object does not exist
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseCopy( CF_Async_Op $cfOp, Status $status, array $params ) {
+               try {
+                       $cfOp->getLastResponse();
+               } catch ( NoSuchObjectException $e ) { // source object does not exist
+                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doMoveInternal()
+        * @return Status
+        */
+       protected function doMoveInternal( array $params ) {
+               $status = Status::newGood();
+
+               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+               if ( $srcRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+                       return $status;
+               }
+
+               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
+               if ( $dstRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+                       return $status;
+               }
+
+               // (a) Check the source/destination containers and destination object
+               try {
+                       $sContObj = $this->getContainer( $srcCont );
+                       $dContObj = $this->getContainer( $dstCont );
+                       if ( empty( $params['overwrite'] ) &&
+                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
+                       {
+                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+                               return $status;
+                       }
+               } catch ( NoSuchContainerException $e ) {
+                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+                       return $status;
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               // (b) Actually move the file to the destination
+               try {
+                       $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+                       $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
+                       if ( !empty( $params['async'] ) ) { // deferred
+                               $handle = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel );
+                               $status->value = new SwiftFileOpHandle( $this, $params, 'Move', $handle );
+                               $status->value->affectedObjects[] = $srcObj;
+                               $status->value->affectedObjects[] = $dstObj;
+                       } else { // actually write the object in Swift
+                               $sContObj->move_object_to( $srcRel, $dContObj, $dstRel );
+                               $this->purgeCDNCache( array( $srcObj, $dstObj ) );
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( NoSuchObjectException $e ) { // source object does not exist
+                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseMove( CF_Async_Op $cfOp, Status $status, array $params ) {
+               try {
+                       $cfOp->getLastResponse();
+               } catch ( NoSuchObjectException $e ) { // source object does not exist
+                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doDeleteInternal()
+        * @return Status
+        */
+       protected function doDeleteInternal( array $params ) {
+               $status = Status::newGood();
+
+               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+               if ( $srcRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+                       return $status;
+               }
+
+               try {
+                       $sContObj = $this->getContainer( $srcCont );
+                       $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+                       if ( !empty( $params['async'] ) ) { // deferred
+                               $handle = $sContObj->delete_object_async( $srcRel );
+                               $status->value = new SwiftFileOpHandle( $this, $params, 'Delete', $handle );
+                               $status->value->affectedObjects[] = $srcObj;
+                       } else { // actually write the object in Swift
+                               $sContObj->delete_object( $srcRel );
+                               $this->purgeCDNCache( array( $srcObj ) );
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( NoSuchContainerException $e ) {
+                       $status->fatal( 'backend-fail-delete', $params['src'] );
+               } catch ( NoSuchObjectException $e ) {
+                       if ( empty( $params['ignoreMissingSource'] ) ) {
+                               $status->fatal( 'backend-fail-delete', $params['src'] );
+                       }
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
+        */
+       protected function _getResponseDelete( CF_Async_Op $cfOp, Status $status, array $params ) {
+               try {
+                       $cfOp->getLastResponse();
+               } catch ( NoSuchContainerException $e ) {
+                       $status->fatal( 'backend-fail-delete', $params['src'] );
+               } catch ( NoSuchObjectException $e ) {
+                       if ( empty( $params['ignoreMissingSource'] ) ) {
+                               $status->fatal( 'backend-fail-delete', $params['src'] );
+                       }
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doPrepareInternal()
+        * @return Status
+        */
+       protected function doPrepareInternal( $fullCont, $dir, array $params ) {
+               $status = Status::newGood();
+
+               // (a) Check if container already exists
+               try {
+                       $contObj = $this->getContainer( $fullCont );
+                       // NoSuchContainerException not thrown: container must exist
+                       return $status; // already exists
+               } catch ( NoSuchContainerException $e ) {
+                       // NoSuchContainerException thrown: container does not exist
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               // (b) Create container as needed
+               try {
+                       $contObj = $this->createContainer( $fullCont );
+                       if ( !empty( $params['noAccess'] ) ) {
+                               // Make container private to end-users...
+                               $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
+                       } else {
+                               // Make container public to end-users...
+                               $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
+                       }
+                       if ( $this->swiftUseCDN ) { // Rackspace style CDN
+                               $contObj->make_public( $this->swiftCDNExpiry );
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doSecureInternal()
+        * @return Status
+        */
+       protected function doSecureInternal( $fullCont, $dir, array $params ) {
+               $status = Status::newGood();
+               if ( empty( $params['noAccess'] ) ) {
+                       return $status; // nothing to do
+               }
+
+               // Restrict container from end-users...
+               try {
+                       // doPrepareInternal() should have been called,
+                       // so the Swift container should already exist...
+                       $contObj = $this->getContainer( $fullCont ); // normally a cache hit
+                       // NoSuchContainerException not thrown: container must exist
+
+                       // Make container private to end-users...
+                       $status->merge( $this->setContainerAccess(
+                               $contObj,
+                               array( $this->auth->username ), // read
+                               array( $this->auth->username ) // write
+                       ) );
+                       if ( $this->swiftUseCDN && $contObj->is_public() ) { // Rackspace style CDN
+                               $contObj->make_private();
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doPublishInternal()
+        * @return Status
+        */
+       protected function doPublishInternal( $fullCont, $dir, array $params ) {
+               $status = Status::newGood();
+
+               // Unrestrict container from end-users...
+               try {
+                       // doPrepareInternal() should have been called,
+                       // so the Swift container should already exist...
+                       $contObj = $this->getContainer( $fullCont ); // normally a cache hit
+                       // NoSuchContainerException not thrown: container must exist
+
+                       // Make container public to end-users...
+                       if ( $this->swiftAnonUser != '' ) {
+                               $status->merge( $this->setContainerAccess(
+                                       $contObj,
+                                       array( $this->auth->username, $this->swiftAnonUser ), // read
+                                       array( $this->auth->username, $this->swiftAnonUser ) // write
+                               ) );
+                       } else {
+                               $status->merge( $this->setContainerAccess(
+                                       $contObj,
+                                       array( $this->auth->username, '.r:*' ), // read
+                                       array( $this->auth->username ) // write
+                               ) );
+                       }
+                       if ( $this->swiftUseCDN && !$contObj->is_public() ) { // Rackspace style CDN
+                               $contObj->make_public();
+                       }
+               } catch ( CDNNotEnabledException $e ) {
+                       // CDN not enabled; nothing to see here
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doCleanInternal()
+        * @return Status
+        */
+       protected function doCleanInternal( $fullCont, $dir, array $params ) {
+               $status = Status::newGood();
+
+               // Only containers themselves can be removed, all else is virtual
+               if ( $dir != '' ) {
+                       return $status; // nothing to do
+               }
+
+               // (a) Check the container
+               try {
+                       $contObj = $this->getContainer( $fullCont, true );
+               } catch ( NoSuchContainerException $e ) {
+                       return $status; // ok, nothing to do
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               // (b) Delete the container if empty
+               if ( $contObj->object_count == 0 ) {
+                       try {
+                               $this->deleteContainer( $fullCont );
+                       } catch ( NoSuchContainerException $e ) {
+                               return $status; // race?
+                       } catch ( NonEmptyContainerException $e ) {
+                               return $status; // race? consistency delay?
+                       } catch ( CloudFilesException $e ) { // some other exception?
+                               $this->handleException( $e, $status, __METHOD__, $params );
+                               return $status;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::doFileExists()
+        * @return array|bool|null
+        */
+       protected function doGetFileStat( array $params ) {
+               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+               if ( $srcRel === null ) {
+                       return false; // invalid storage path
+               }
+
+               $stat = false;
+               try {
+                       $contObj = $this->getContainer( $srcCont );
+                       $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) );
+                       $this->addMissingMetadata( $srcObj, $params['src'] );
+                       $stat = array(
+                               // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
+                               'mtime' => wfTimestamp( TS_MW, $srcObj->last_modified ),
+                               'size'  => (int)$srcObj->content_length,
+                               'sha1'  => $srcObj->metadata['Sha1base36']
+                       );
+               } catch ( NoSuchContainerException $e ) {
+               } catch ( NoSuchObjectException $e ) {
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $stat = null;
+                       $this->handleException( $e, null, __METHOD__, $params );
+               }
+
+               return $stat;
+       }
+
+       /**
+        * Fill in any missing object metadata and save it to Swift
+        *
+        * @param $obj CF_Object
+        * @param $path string Storage path to object
+        * @return bool Success
+        * @throws Exception cloudfiles exceptions
+        */
+       protected function addMissingMetadata( CF_Object $obj, $path ) {
+               if ( isset( $obj->metadata['Sha1base36'] ) ) {
+                       return true; // nothing to do
+               }
+               $status = Status::newGood();
+               $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status );
+               if ( $status->isOK() ) {
+                       # Do not stat the file in getLocalCopy() to avoid infinite loops
+                       $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1, 'nostat' => 1 ) );
+                       if ( $tmpFile ) {
+                               $hash = $tmpFile->getSha1Base36();
+                               if ( $hash !== false ) {
+                                       $obj->metadata['Sha1base36'] = $hash;
+                                       $obj->sync_metadata(); // save to Swift
+                                       return true; // success
+                               }
+                       }
+               }
+               $obj->metadata['Sha1base36'] = false;
+               return false; // failed
+       }
+
+       /**
+        * @see FileBackend::getFileContents()
+        * @return bool|null|string
+        */
+       public function getFileContents( array $params ) {
+               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+               if ( $srcRel === null ) {
+                       return false; // invalid storage path
+               }
+
+               if ( !$this->fileExists( $params ) ) {
+                       return null;
+               }
+
+               $data = false;
+               try {
+                       $sContObj = $this->getContainer( $srcCont );
+                       $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+                       $data = $obj->read( $this->headersFromParams( $params ) );
+               } catch ( NoSuchContainerException $e ) {
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, null, __METHOD__, $params );
+               }
+
+               return $data;
+       }
+
+       /**
+        * @see FileBackendStore::doDirectoryExists()
+        * @return bool|null
+        */
+       protected function doDirectoryExists( $fullCont, $dir, array $params ) {
+               try {
+                       $container = $this->getContainer( $fullCont );
+                       $prefix = ( $dir == '' ) ? null : "{$dir}/";
+                       return ( count( $container->list_objects( 1, null, $prefix ) ) > 0 );
+               } catch ( NoSuchContainerException $e ) {
+                       return false;
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, null, __METHOD__,
+                               array( 'cont' => $fullCont, 'dir' => $dir ) );
+               }
+
+               return null; // error
+       }
+
+       /**
+        * @see FileBackendStore::getDirectoryListInternal()
+        * @return SwiftFileBackendDirList
+        */
+       public function getDirectoryListInternal( $fullCont, $dir, array $params ) {
+               return new SwiftFileBackendDirList( $this, $fullCont, $dir, $params );
+       }
+
+       /**
+        * @see FileBackendStore::getFileListInternal()
+        * @return SwiftFileBackendFileList
+        */
+       public function getFileListInternal( $fullCont, $dir, array $params ) {
+               return new SwiftFileBackendFileList( $this, $fullCont, $dir, $params );
+       }
+
+       /**
+        * Do not call this function outside of SwiftFileBackendFileList
+        *
+        * @param $fullCont string Resolved container name
+        * @param $dir string Resolved storage directory with no trailing slash
+        * @param $after string|null Storage path of file to list items after
+        * @param $limit integer Max number of items to list
+        * @param $params Array Includes flag for 'topOnly'
+        * @return Array List of relative paths of dirs directly under $dir
+        */
+       public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
+               $dirs = array();
+               if ( $after === INF ) {
+                       return $dirs; // nothing more
+               }
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+
+               try {
+                       $container = $this->getContainer( $fullCont );
+                       $prefix = ( $dir == '' ) ? null : "{$dir}/";
+                       // Non-recursive: only list dirs right under $dir
+                       if ( !empty( $params['topOnly'] ) ) {
+                               $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
+                               foreach ( $objects as $object ) { // files and dirs
+                                       if ( substr( $object, -1 ) === '/' ) {
+                                               $dirs[] = $object; // directories end in '/'
+                                       }
+                               }
+                       // Recursive: list all dirs under $dir and its subdirs
+                       } else {
+                               // Get directory from last item of prior page
+                               $lastDir = $this->getParentDir( $after ); // must be first page
+                               $objects = $container->list_objects( $limit, $after, $prefix );
+                               foreach ( $objects as $object ) { // files
+                                       $objectDir = $this->getParentDir( $object ); // directory of object
+                                       if ( $objectDir !== false ) { // file has a parent dir
+                                               // Swift stores paths in UTF-8, using binary sorting.
+                                               // See function "create_container_table" in common/db.py.
+                                               // If a directory is not "greater" than the last one,
+                                               // then it was already listed by the calling iterator.
+                                               if ( $objectDir > $lastDir ) {
+                                                       $pDir = $objectDir;
+                                                       do { // add dir and all its parent dirs
+                                                               $dirs[] = "{$pDir}/";
+                                                               $pDir = $this->getParentDir( $pDir );
+                                                       } while ( $pDir !== false // sanity
+                                                               && $pDir > $lastDir // not done already
+                                                               && strlen( $pDir ) > strlen( $dir ) // within $dir
+                                                       );
+                                               }
+                                               $lastDir = $objectDir;
+                                       }
+                               }
+                       }
+                       if ( count( $objects ) < $limit ) {
+                               $after = INF; // avoid a second RTT
+                       } else {
+                               $after = end( $objects ); // update last item
+                       }
+               } catch ( NoSuchContainerException $e ) {
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, null, __METHOD__,
+                               array( 'cont' => $fullCont, 'dir' => $dir ) );
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               return $dirs;
+       }
+
+       protected function getParentDir( $path ) {
+               return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false;
+       }
+
+       /**
+        * Do not call this function outside of SwiftFileBackendFileList
+        *
+        * @param $fullCont string Resolved container name
+        * @param $dir string Resolved storage directory with no trailing slash
+        * @param $after string|null Storage path of file to list items after
+        * @param $limit integer Max number of items to list
+        * @param $params Array Includes flag for 'topOnly'
+        * @return Array List of relative paths of files under $dir
+        */
+       public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
+               $files = array();
+               if ( $after === INF ) {
+                       return $files; // nothing more
+               }
+               wfProfileIn( __METHOD__ . '-' . $this->name );
+
+               try {
+                       $container = $this->getContainer( $fullCont );
+                       $prefix = ( $dir == '' ) ? null : "{$dir}/";
+                       // Non-recursive: only list files right under $dir
+                       if ( !empty( $params['topOnly'] ) ) { // files and dirs
+                               $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
+                               foreach ( $objects as $object ) {
+                                       if ( substr( $object, -1 ) !== '/' ) {
+                                               $files[] = $object; // directories end in '/'
+                                       }
+                               }
+                       // Recursive: list all files under $dir and its subdirs
+                       } else { // files
+                               $objects = $container->list_objects( $limit, $after, $prefix );
+                               $files = $objects;
+                       }
+                       if ( count( $objects ) < $limit ) {
+                               $after = INF; // avoid a second RTT
+                       } else {
+                               $after = end( $objects ); // update last item
+                       }
+               } catch ( NoSuchContainerException $e ) {
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, null, __METHOD__,
+                               array( 'cont' => $fullCont, 'dir' => $dir ) );
+               }
+
+               wfProfileOut( __METHOD__ . '-' . $this->name );
+               return $files;
+       }
+
+       /**
+        * @see FileBackendStore::doGetFileSha1base36()
+        * @return bool
+        */
+       protected function doGetFileSha1base36( array $params ) {
+               $stat = $this->getFileStat( $params );
+               if ( $stat ) {
+                       return $stat['sha1'];
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * @see FileBackendStore::doStreamFile()
+        * @return Status
+        */
+       protected function doStreamFile( array $params ) {
+               $status = Status::newGood();
+
+               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+               if ( $srcRel === null ) {
+                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+               }
+
+               try {
+                       $cont = $this->getContainer( $srcCont );
+               } catch ( NoSuchContainerException $e ) {
+                       $status->fatal( 'backend-fail-stream', $params['src'] );
+                       return $status;
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+                       return $status;
+               }
+
+               try {
+                       $output = fopen( 'php://output', 'wb' );
+                       $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD
+                       $obj->stream( $output, $this->headersFromParams( $params ) );
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, $status, __METHOD__, $params );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FileBackendStore::getLocalCopy()
+        * @return null|TempFSFile
+        */
+       public function getLocalCopy( array $params ) {
+               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+               if ( $srcRel === null ) {
+                       return null;
+               }
+
+               # Check the recursion guard to avoid loops when filling metadata
+               if ( empty( $params['nostat'] ) && !$this->fileExists( $params ) ) {
+                       return null;
+               }
+
+               $tmpFile = null;
+               try {
+                       $sContObj = $this->getContainer( $srcCont );
+                       $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+                       // Get source file extension
+                       $ext = FileBackend::extensionFromPath( $srcRel );
+                       // Create a new temporary file...
+                       $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext );
+                       if ( $tmpFile ) {
+                               $handle = fopen( $tmpFile->getPath(), 'wb' );
+                               if ( $handle ) {
+                                       $obj->stream( $handle, $this->headersFromParams( $params ) );
+                                       fclose( $handle );
+                               } else {
+                                       $tmpFile = null; // couldn't open temp file
+                               }
+                       }
+               } catch ( NoSuchContainerException $e ) {
+                       $tmpFile = null;
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $tmpFile = null;
+                       $this->handleException( $e, null, __METHOD__, $params );
+               }
+
+               return $tmpFile;
+       }
+
+       /**
+        * @see FileBackendStore::directoriesAreVirtual()
+        * @return bool
+        */
+       protected function directoriesAreVirtual() {
+               return true;
+       }
+
+       /**
+        * Get headers to send to Swift when reading a file based
+        * on a FileBackend params array, e.g. that of getLocalCopy().
+        * $params is currently only checked for a 'latest' flag.
+        *
+        * @param $params Array
+        * @return Array
+        */
+       protected function headersFromParams( array $params ) {
+               $hdrs = array();
+               if ( !empty( $params['latest'] ) ) {
+                       $hdrs[] = 'X-Newest: true';
+               }
+               return $hdrs;
+       }
+
+       /**
+        * @see FileBackendStore::doExecuteOpHandlesInternal()
+        * @return Array List of corresponding Status objects
+        */
+       protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
+               $statuses = array();
+
+               $cfOps = array(); // list of CF_Async_Op objects
+               foreach ( $fileOpHandles as $index => $fileOpHandle ) {
+                       $cfOps[$index] = $fileOpHandle->cfOp;
+               }
+               $batch = new CF_Async_Op_Batch( $cfOps );
+
+               $cfOps = $batch->execute();
+               foreach ( $cfOps as $index => $cfOp ) {
+                       $status = Status::newGood();
+                       try { // catch exceptions; update status
+                               $function = '_getResponse' . $fileOpHandles[$index]->call;
+                               $this->$function( $cfOp, $status, $fileOpHandles[$index]->params );
+                               $this->purgeCDNCache( $fileOpHandles[$index]->affectedObjects );
+                       } catch ( CloudFilesException $e ) { // some other exception?
+                               $this->handleException( $e, $status,
+                                       __CLASS__ . ":$function", $fileOpHandles[$index]->params );
+                       }
+                       $statuses[$index] = $status;
+               }
+
+               return $statuses;
+       }
+
+       /**
+        * Set read/write permissions for a Swift container.
+        *
+        * $readGrps is a list of the possible criteria for a request to have
+        * access to read a container. Each item is one of the following formats:
+        *   - account:user       : Grants access if the request is by the given user
+        *   - .r:<regex>         : Grants access if the request is from a referrer host that
+        *                          matches the expression and the request is not for a listing.
+        *                          Setting this to '*' effectively makes a container public.
+        *   - .rlistings:<regex> : Grants access if the request is from a referrer host that
+        *                          matches the expression and the request for a listing.
+        *
+        * $writeGrps is a list of the possible criteria for a request to have
+        * access to write to a container. Each item is of the following format:
+        *   - account:user       : Grants access if the request is by the given user
+        *
+        * @see http://swift.openstack.org/misc.html#acls
+        *
+        * In general, we don't allow listings to end-users. It's not useful, isn't well-defined
+        * (lists are truncated to 10000 item with no way to page), and is just a performance risk.
+        *
+        * @param $contObj CF_Container Swift container
+        * @param $readGrps Array List of read access routes
+        * @param $writeGrps Array List of write access routes
+        * @return Status
+        */
+       protected function setContainerAccess(
+               CF_Container $contObj, array $readGrps, array $writeGrps
+       ) {
+               $creds = $contObj->cfs_auth->export_credentials();
+
+               $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name );
+
+               // Note: 10 second timeout consistent with php-cloudfiles
+               $req = MWHttpRequest::factory( $url, array( 'method' => 'POST', 'timeout' => 10 ) );
+               $req->setHeader( 'X-Auth-Token', $creds['auth_token'] );
+               $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) );
+               $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) );
+
+               return $req->execute(); // should return 204
+       }
+
+       /**
+        * Purge the CDN cache of affected objects if CDN caching is enabled.
+        * This is for Rackspace/Akamai CDNs.
+        *
+        * @param $objects Array List of CF_Object items
+        * @return void
+        */
+       public function purgeCDNCache( array $objects ) {
+               if ( $this->swiftUseCDN && $this->swiftCDNPurgable ) {
+                       foreach ( $objects as $object ) {
+                               try {
+                                       $object->purge_from_cdn();
+                               } catch ( CDNNotEnabledException $e ) {
+                                       // CDN not enabled; nothing to see here
+                               } catch ( CloudFilesException $e ) {
+                                       $this->handleException( $e, null, __METHOD__,
+                                               array( 'cont' => $object->container->name, 'obj' => $object->name ) );
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Get a connection to the Swift proxy
+        *
+        * @return CF_Connection|bool False on failure
+        * @throws CloudFilesException
+        */
+       protected function getConnection() {
+               if ( $this->connException instanceof Exception ) {
+                       throw $this->connException; // failed last attempt
+               }
+               // Session keys expire after a while, so we renew them periodically
+               if ( $this->conn && ( time() - $this->connStarted ) > $this->authTTL ) {
+                       $this->conn->close(); // close active cURL connections
+                       $this->conn = null;
+               }
+               // Authenticate with proxy and get a session key...
+               if ( !$this->conn ) {
+                       $this->connStarted = 0;
+                       $this->connContainers = array();
+                       try {
+                               $this->auth->authenticate();
+                               $this->conn = new CF_Connection( $this->auth );
+                               $this->connStarted = time();
+                       } catch ( CloudFilesException $e ) {
+                               $this->connException = $e; // don't keep re-trying
+                               throw $e; // throw it back
+                       }
+               }
+               return $this->conn;
+       }
+
+       /**
+        * @see FileBackendStore::doClearCache()
+        */
+       protected function doClearCache( array $paths = null ) {
+               $this->connContainers = array(); // clear container object cache
+       }
+
+       /**
+        * Get a Swift container object, possibly from process cache.
+        * Use $reCache if the file count or byte count is needed.
+        *
+        * @param $container string Container name
+        * @param $bypassCache bool Bypass all caches and load from Swift
+        * @return CF_Container
+        * @throws CloudFilesException
+        */
+       protected function getContainer( $container, $bypassCache = false ) {
+               $conn = $this->getConnection(); // Swift proxy connection
+               if ( $bypassCache ) { // purge cache
+                       unset( $this->connContainers[$container] );
+               } elseif ( !isset( $this->connContainers[$container] ) ) {
+                       $this->primeContainerCache( array( $container ) ); // check persistent cache
+               }
+               if ( !isset( $this->connContainers[$container] ) ) {
+                       $contObj = $conn->get_container( $container );
+                       // NoSuchContainerException not thrown: container must exist
+                       if ( count( $this->connContainers ) >= $this->maxContCacheSize ) { // trim cache?
+                               reset( $this->connContainers );
+                               unset( $this->connContainers[key( $this->connContainers )] );
+                       }
+                       $this->connContainers[$container] = $contObj; // cache it
+                       if ( !$bypassCache ) {
+                               $this->setContainerCache( $container, // update persistent cache
+                                       array( 'bytes' => $contObj->bytes_used, 'count' => $contObj->object_count )
+                               );
+                       }
+               }
+               return $this->connContainers[$container];
+       }
+
+       /**
+        * Create a Swift container
+        *
+        * @param $container string Container name
+        * @return CF_Container
+        * @throws CloudFilesException
+        */
+       protected function createContainer( $container ) {
+               $conn = $this->getConnection(); // Swift proxy connection
+               $contObj = $conn->create_container( $container );
+               $this->connContainers[$container] = $contObj; // cache it
+               return $contObj;
+       }
+
+       /**
+        * Delete a Swift container
+        *
+        * @param $container string Container name
+        * @return void
+        * @throws CloudFilesException
+        */
+       protected function deleteContainer( $container ) {
+               $conn = $this->getConnection(); // Swift proxy connection
+               unset( $this->connContainers[$container] ); // purge cache
+               $conn->delete_container( $container );
+       }
+
+       /**
+        * @see FileBackendStore::doPrimeContainerCache()
+        * @return void
+        */
+       protected function doPrimeContainerCache( array $containerInfo ) {
+               try {
+                       $conn = $this->getConnection(); // Swift proxy connection
+                       foreach ( $containerInfo as $container => $info ) {
+                               $this->connContainers[$container] = new CF_Container(
+                                       $conn->cfs_auth,
+                                       $conn->cfs_http,
+                                       $container,
+                                       $info['count'],
+                                       $info['bytes']
+                               );
+                       }
+               } catch ( CloudFilesException $e ) { // some other exception?
+                       $this->handleException( $e, null, __METHOD__, array() );
+               }
+       }
+
+       /**
+        * Log an unexpected exception for this backend.
+        * This also sets the Status object to have a fatal error.
+        *
+        * @param $e Exception
+        * @param $status Status|null
+        * @param $func string
+        * @param $params Array
+        * @return void
+        */
+       protected function handleException( Exception $e, $status, $func, array $params ) {
+               if ( $status instanceof Status ) {
+                       if ( $e instanceof AuthenticationException ) {
+                               $status->fatal( 'backend-fail-connect', $this->name );
+                       } else {
+                               $status->fatal( 'backend-fail-internal', $this->name );
+                       }
+               }
+               if ( $e->getMessage() ) {
+                       trigger_error( "$func: " . $e->getMessage(), E_USER_WARNING );
+               }
+               wfDebugLog( 'SwiftBackend',
+                       get_class( $e ) . " in '{$func}' (given '" . FormatJson::encode( $params ) . "')" .
+                       ( $e->getMessage() ? ": {$e->getMessage()}" : "" )
+               );
+       }
+}
+
+/**
+ * @see FileBackendStoreOpHandle
+ */
+class SwiftFileOpHandle extends FileBackendStoreOpHandle {
+       /** @var CF_Async_Op */
+       public $cfOp;
+       /** @var Array */
+       public $affectedObjects = array();
+
+       public function __construct( $backend, array $params, $call, CF_Async_Op $cfOp ) {
+               $this->backend = $backend;
+               $this->params = $params;
+               $this->call = $call;
+               $this->cfOp = $cfOp;
+       }
+}
+
+/**
+ * SwiftFileBackend helper class to page through listings.
+ * Swift also has a listing limit of 10,000 objects for sanity.
+ * Do not use this class from places outside SwiftFileBackend.
+ *
+ * @ingroup FileBackend
+ */
+abstract class SwiftFileBackendList implements Iterator {
+       /** @var Array */
+       protected $bufferIter = array();
+       protected $bufferAfter = null; // string; list items *after* this path
+       protected $pos = 0; // integer
+       /** @var Array */
+       protected $params = array();
+
+       /** @var SwiftFileBackend */
+       protected $backend;
+       protected $container; // string; container name
+       protected $dir; // string; storage directory
+       protected $suffixStart; // integer
+
+       const PAGE_SIZE = 9000; // file listing buffer size
+
+       /**
+        * @param $backend SwiftFileBackend
+        * @param $fullCont string Resolved container name
+        * @param $dir string Resolved directory relative to container
+        * @param $params Array
+        */
+       public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
+               $this->backend = $backend;
+               $this->container = $fullCont;
+               $this->dir = $dir;
+               if ( substr( $this->dir, -1 ) === '/' ) {
+                       $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash
+               }
+               if ( $this->dir == '' ) { // whole container
+                       $this->suffixStart = 0;
+               } else { // dir within container
+                       $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
+               }
+               $this->params = $params;
+       }
+
+       /**
+        * @see Iterator::key()
+        * @return integer
+        */
+       public function key() {
+               return $this->pos;
+       }
+
+       /**
+        * @see Iterator::next()
+        * @return void
+        */
+       public function next() {
+               // Advance to the next file in the page
+               next( $this->bufferIter );
+               ++$this->pos;
+               // Check if there are no files left in this page and
+               // advance to the next page if this page was not empty.
+               if ( !$this->valid() && count( $this->bufferIter ) ) {
+                       $this->bufferIter = $this->pageFromList(
+                               $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
+                       ); // updates $this->bufferAfter
+               }
+       }
+
+       /**
+        * @see Iterator::rewind()
+        * @return void
+        */
+       public function rewind() {
+               $this->pos = 0;
+               $this->bufferAfter = null;
+               $this->bufferIter = $this->pageFromList(
+                       $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
+               ); // updates $this->bufferAfter
+       }
+
+       /**
+        * @see Iterator::valid()
+        * @return bool
+        */
+       public function valid() {
+               if ( $this->bufferIter === null ) {
+                       return false; // some failure?
+               } else {
+                       return ( current( $this->bufferIter ) !== false ); // no paths can have this value
+               }
+       }
+
+       /**
+        * Get the given list portion (page)
+        *
+        * @param $container string Resolved container name
+        * @param $dir string Resolved path relative to container
+        * @param $after string|null
+        * @param $limit integer
+        * @param $params Array
+        * @return Traversable|Array|null Returns null on failure
+        */
+       abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
+}
+
+/**
+ * Iterator for listing directories
+ */
+class SwiftFileBackendDirList extends SwiftFileBackendList {
+       /**
+        * @see Iterator::current()
+        * @return string|bool String (relative path) or false
+        */
+       public function current() {
+               return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
+       }
+
+       /**
+        * @see SwiftFileBackendList::pageFromList()
+        * @return Array|null
+        */
+       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
+               return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
+       }
+}
+
+/**
+ * Iterator for listing regular files
+ */
+class SwiftFileBackendFileList extends SwiftFileBackendList {
+       /**
+        * @see Iterator::current()
+        * @return string|bool String (relative path) or false
+        */
+       public function current() {
+               return substr( current( $this->bufferIter ), $this->suffixStart );
+       }
+
+       /**
+        * @see SwiftFileBackendList::pageFromList()
+        * @return Array|null
+        */
+       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
+               return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
+       }
+}
diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php
new file mode 100644 (file)
index 0000000..ddc640a
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * Location holder of files stored temporarily
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ */
+
+/**
+ * This class is used to hold the location and do limited manipulation
+ * of files stored temporarily (this will be whatever wfTempDir() returns)
+ *
+ * @ingroup FileBackend
+ */
+class TempFSFile extends FSFile {
+       protected $canDelete = false; // bool; garbage collect the temp file
+
+       /** @var Array of active temp files to purge on shutdown */
+       protected static $instances = array();
+
+       /**
+        * Make a new temporary file on the file system.
+        * Temporary files may be purged when the file object falls out of scope.
+        * 
+        * @param $prefix string
+        * @param $extension string
+        * @return TempFSFile|null 
+        */
+       public static function factory( $prefix, $extension = '' ) {
+               $base = wfTempDir() . '/' . $prefix . dechex( mt_rand( 0, 99999999 ) );
+               $ext = ( $extension != '' ) ? ".{$extension}" : "";
+               for ( $attempt = 1; true; $attempt++ ) {
+                       $path = "{$base}-{$attempt}{$ext}";
+                       wfSuppressWarnings();
+                       $newFileHandle = fopen( $path, 'x' );
+                       wfRestoreWarnings();
+                       if ( $newFileHandle ) {
+                               fclose( $newFileHandle );
+                               break; // got it
+                       }
+                       if ( $attempt >= 15 ) {
+                               return null; // give up
+                       }
+               }
+               $tmpFile = new self( $path );
+               $tmpFile->canDelete = true; // safely instantiated
+               return $tmpFile;
+       }
+
+       /**
+        * Purge this file off the file system
+        * 
+        * @return bool Success
+        */
+       public function purge() {
+               $this->canDelete = false; // done
+               wfSuppressWarnings();
+               $ok = unlink( $this->path );
+               wfRestoreWarnings();
+               return $ok;
+       }
+
+       /**
+        * Clean up the temporary file only after an object goes out of scope
+        *
+        * @param $object Object
+        * @return void
+        */
+       public function bind( $object ) {
+               if ( is_object( $object ) ) {
+                       $object->tempFSFileReferences[] = $this;
+               }
+       }
+
+       /**
+        * Set flag to not clean up after the temporary file
+        *
+        * @return void
+        */
+       public function preserve() {
+               $this->canDelete = false;
+       }
+
+       /**
+        * Cleans up after the temporary file by deleting it
+        */
+       function __destruct() {
+               if ( $this->canDelete ) {
+                       wfSuppressWarnings();
+                       unlink( $this->path );
+                       wfRestoreWarnings();
+               }
+       }
+}
diff --git a/includes/filebackend/filejournal/DBFileJournal.php b/includes/filebackend/filejournal/DBFileJournal.php
new file mode 100644 (file)
index 0000000..8fa4c6f
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Version of FileJournal that logs to a DB table.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileJournal
+ * @author Aaron Schulz
+ */
+
+/**
+ * Version of FileJournal that logs to a DB table
+ * @since 1.20
+ */
+class DBFileJournal extends FileJournal {
+       protected $wiki = false; // string; wiki DB name
+
+       /**
+        * Construct a new instance from configuration.
+        * $config includes:
+        *     'wiki' : wiki name to use for LoadBalancer
+        *
+        * @param $config Array
+        */
+       protected function __construct( array $config ) {
+               parent::__construct( $config );
+
+               $this->wiki = $config['wiki'];
+       }
+
+       /**
+        * @see FileJournal::logChangeBatch()
+        * @return Status
+        */
+       protected function doLogChangeBatch( array $entries, $batchId ) {
+               $status = Status::newGood();
+
+               try {
+                       $dbw = $this->getMasterDB();
+               } catch ( DBError $e ) {
+                       $status->fatal( 'filejournal-fail-dbconnect', $this->backend );
+                       return $status;
+               }
+
+               $now = wfTimestamp( TS_UNIX );
+
+               $data = array();
+               foreach ( $entries as $entry ) {
+                       $data[] = array(
+                               'fj_batch_uuid' => $batchId,
+                               'fj_backend'    => $this->backend,
+                               'fj_op'         => $entry['op'],
+                               'fj_path'       => $entry['path'],
+                               'fj_new_sha1'   => $entry['newSha1'],
+                               'fj_timestamp'  => $dbw->timestamp( $now )
+                       );
+               }
+
+               try {
+                       $dbw->begin();
+                       $dbw->insert( 'filejournal', $data, __METHOD__ );
+                       $dbw->commit();
+               } catch ( DBError $e ) {
+                       $status->fatal( 'filejournal-fail-dbquery', $this->backend );
+                       return $status;
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see FileJournal::doGetChangeEntries()
+        * @return Array
+        * @throws DBError
+        */
+       protected function doGetChangeEntries( $start, $limit ) {
+               $dbw = $this->getMasterDB();
+
+               $res = $dbw->select( 'filejournal', '*',
+                       array(
+                               'fj_backend' => $this->backend,
+                               'fj_id >= ' . $dbw->addQuotes( (int)$start ) ), // $start may be 0
+                       __METHOD__,
+                       array_merge( array( 'ORDER BY' => 'fj_id ASC' ),
+                               $limit ? array( 'LIMIT' => $limit ) : array() )
+               );
+
+               $entries = array();
+               foreach ( $res as $row ) {
+                       $item = array();
+                       foreach ( (array)$row as $key => $value ) {
+                               $item[substr( $key, 3 )] = $value; // "fj_op" => "op"
+                       }
+                       $entries[] = $item;
+               }
+
+               return $entries;
+       }
+
+       /**
+        * @see FileJournal::purgeOldLogs()
+        * @return Status
+        * @throws DBError
+        */
+       protected function doPurgeOldLogs() {
+               $status = Status::newGood();
+               if ( $this->ttlDays <= 0 ) {
+                       return $status; // nothing to do
+               }
+
+               $dbw = $this->getMasterDB();
+               $dbCutoff = $dbw->timestamp( time() - 86400 * $this->ttlDays );
+
+               $dbw->begin();
+               $dbw->delete( 'filejournal',
+                       array( 'fj_timestamp < ' . $dbw->addQuotes( $dbCutoff ) ),
+                       __METHOD__
+               );
+               $dbw->commit();
+
+               return $status;
+       }
+
+       /**
+        * Get a master connection to the logging DB
+        *
+        * @return DatabaseBase
+        * @throws DBError
+        */
+       protected function getMasterDB() {
+               $lb = wfGetLBFactory()->newMainLB();
+               return $lb->getConnection( DB_MASTER, array(), $this->wiki );
+       }
+}
diff --git a/includes/filebackend/filejournal/FileJournal.php b/includes/filebackend/filejournal/FileJournal.php
new file mode 100644 (file)
index 0000000..234788b
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+/**
+ * @defgroup FileJournal File journal
+ * @ingroup FileBackend
+ */
+
+/**
+ * File operation journaling.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileJournal
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Class for handling file operation journaling.
+ *
+ * Subclasses should avoid throwing exceptions at all costs.
+ *
+ * @ingroup FileJournal
+ * @since 1.20
+ */
+abstract class FileJournal {
+       protected $backend; // string
+       protected $ttlDays; // integer
+
+       /**
+        * Construct a new instance from configuration.
+        * $config includes:
+        *     'ttlDays' : days to keep log entries around (false means "forever")
+        * 
+        * @param $config Array
+        */
+       protected function __construct( array $config ) {
+               $this->ttlDays = isset( $config['ttlDays'] ) ? $config['ttlDays'] : false;
+       }
+
+       /**
+        * Create an appropriate FileJournal object from config
+        *
+        * @param $config Array
+        * @param $backend string A registered file backend name
+        * @throws MWException
+        * @return FileJournal
+        */
+       final public static function factory( array $config, $backend ) {
+               $class = $config['class'];
+               $jrn = new $class( $config );
+               if ( !$jrn instanceof self ) {
+                       throw new MWException( "Class given is not an instance of FileJournal." );
+               }
+               $jrn->backend = $backend;
+               return $jrn;
+       }
+
+       /**
+        * Get a statistically unique ID string
+        * 
+        * @return string <9 char TS_MW timestamp in base 36><22 random base 36 chars>
+        */
+       final public function getTimestampedUUID() {
+               $s = '';
+               for ( $i = 0; $i < 5; $i++ ) {
+                       $s .= mt_rand( 0, 2147483647 );
+               }
+               $s = wfBaseConvert( sha1( $s ), 16, 36, 31 );
+               return substr( wfBaseConvert( wfTimestamp( TS_MW ), 10, 36, 9 ) . $s, 0, 31 );
+       }
+
+       /**
+        * Log changes made by a batch file operation.
+        * $entries is an array of log entries, each of which contains:
+        *     op      : Basic operation name (create, store, copy, delete)
+        *     path    : The storage path of the file
+        *     newSha1 : The final base 36 SHA-1 of the file
+        * Note that 'false' should be used as the SHA-1 for non-existing files.
+        * 
+        * @param $entries Array List of file operations (each an array of parameters)
+        * @param $batchId string UUID string that identifies the operation batch
+        * @return Status
+        */
+       final public function logChangeBatch( array $entries, $batchId ) {
+               if ( !count( $entries ) ) {
+                       return Status::newGood();
+               }
+               return $this->doLogChangeBatch( $entries, $batchId );
+       }
+
+       /**
+        * @see FileJournal::logChangeBatch()
+        * 
+        * @param $entries Array List of file operations (each an array of parameters)
+        * @param $batchId string UUID string that identifies the operation batch
+        * @return Status
+        */
+       abstract protected function doLogChangeBatch( array $entries, $batchId );
+
+       /**
+        * Get an array of file change log entries.
+        * A starting change ID and/or limit can be specified.
+        *
+        * The result as a list of associative arrays, each having:
+        *     id         : unique, monotonic, ID for this change
+        *     batch_uuid : UUID for an operation batch
+        *     backend    : the backend name
+        *     op         : primitive operation (create,update,delete)
+        *     path       : affected storage path
+        *     path_sha1  : base 36 sha1 of the affected storage path
+        *     timestamp  : TS_MW timestamp of the batch change
+
+        * Also, $next is updated to the ID of the next entry.
+        *
+        * @param $start integer Starting change ID or null
+        * @param $limit integer Maximum number of items to return
+        * @param &$next string
+        * @return Array
+        */
+       final public function getChangeEntries( $start = null, $limit = 0, &$next = null ) {
+               $entries = $this->doGetChangeEntries( $start, $limit ? $limit + 1 : 0 );
+               if ( $limit && count( $entries ) > $limit ) {
+                       $last = array_pop( $entries ); // remove the extra entry
+                       $next = $last['id']; // update for next call
+               } else {
+                       $next = null; // end of list
+               }
+               return $entries;
+       }
+
+       /**
+        * @see FileJournal::getChangeEntries()
+        * @return Array
+        */
+       abstract protected function doGetChangeEntries( $start, $limit );
+
+       /**
+        * Purge any old log entries
+        *
+        * @return Status
+        */
+       final public function purgeOldLogs() {
+               return $this->doPurgeOldLogs();
+       }
+
+       /**
+        * @see FileJournal::purgeOldLogs()
+        * @return Status
+        */
+       abstract protected function doPurgeOldLogs();
+}
+
+/**
+ * Simple version of FileJournal that does nothing
+ * @since 1.20
+ */
+class NullFileJournal extends FileJournal {
+       /**
+        * @see FileJournal::logChangeBatch()
+        * @param $entries array
+        * @param $batchId string
+        * @return Status
+        */
+       protected function doLogChangeBatch( array $entries, $batchId ) {
+               return Status::newGood();
+       }
+
+       /**
+        * @see FileJournal::doGetChangeEntries()
+        * @return Array
+        */
+       protected function doGetChangeEntries( $start, $limit ) {
+               return array();
+       }
+
+       /**
+        * @see FileJournal::purgeOldLogs()
+        * @return Status
+        */
+       protected function doPurgeOldLogs() {
+               return Status::newGood();
+       }
+}
diff --git a/includes/filebackend/lockmanager/DBLockManager.php b/includes/filebackend/lockmanager/DBLockManager.php
new file mode 100644 (file)
index 0000000..204ca3b
--- /dev/null
@@ -0,0 +1,374 @@
+<?php
+/**
+ * Version of LockManager based on using DB table locks.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup LockManager
+ */
+
+/**
+ * Version of LockManager based on using DB table locks.
+ * This is meant for multi-wiki systems that may share files.
+ * All locks are blocking, so it might be useful to set a small
+ * lock-wait timeout via server config to curtail deadlocks.
+ *
+ * All lock requests for a resource, identified by a hash string, will map
+ * to one bucket. Each bucket maps to one or several peer DBs, each on their
+ * own server, all having the filelocks.sql tables (with row-level locking).
+ * A majority of peer DBs must agree for a lock to be acquired.
+ *
+ * Caching is used to avoid hitting servers that are down.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class DBLockManager extends QuorumLockManager {
+       /** @var Array Map of DB names to server config */
+       protected $dbServers; // (DB name => server config array)
+       /** @var BagOStuff */
+       protected $statusCache;
+
+       protected $lockExpiry; // integer number of seconds
+       protected $safeDelay; // integer number of seconds
+
+       protected $session = 0; // random integer
+       /** @var Array Map Database connections (DB name => Database) */
+       protected $conns = array();
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * $config paramaters include:
+        *     'dbServers'   : Associative array of DB names to server configuration.
+        *                     Configuration is an associative array that includes:
+        *                     'host'        - DB server name
+        *                     'dbname'      - DB name
+        *                     'type'        - DB type (mysql,postgres,...)
+        *                     'user'        - DB user
+        *                     'password'    - DB user password
+        *                     'tablePrefix' - DB table prefix
+        *                     'flags'       - DB flags (see DatabaseBase)
+        *     'dbsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
+        *                     each having an odd-numbered list of DB names (peers) as values.
+        *                     Any DB named 'localDBMaster' will automatically use the DB master
+        *                     settings for this wiki (without the need for a dbServers entry).
+        *     'lockExpiry'  : Lock timeout (seconds) for dropped connections. [optional]
+        *                     This tells the DB server how long to wait before assuming
+        *                     connection failure and releasing all the locks for a session.
+        *
+        * @param Array $config
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+
+               $this->dbServers = isset( $config['dbServers'] )
+                       ? $config['dbServers']
+                       : array(); // likely just using 'localDBMaster'
+               // Sanitize srvsByBucket config to prevent PHP errors
+               $this->srvsByBucket = array_filter( $config['dbsByBucket'], 'is_array' );
+               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
+
+               if ( isset( $config['lockExpiry'] ) ) {
+                       $this->lockExpiry = $config['lockExpiry'];
+               } else {
+                       $met = ini_get( 'max_execution_time' );
+                       $this->lockExpiry = $met ? $met : 60; // use some sane amount if 0
+               }
+               $this->safeDelay = ( $this->lockExpiry <= 0 )
+                       ? 60 // pick a safe-ish number to match DB timeout default
+                       : $this->lockExpiry; // cover worst case
+
+               foreach ( $this->srvsByBucket as $bucket ) {
+                       if ( count( $bucket ) > 1 ) { // multiple peers
+                               // Tracks peers that couldn't be queried recently to avoid lengthy
+                               // connection timeouts. This is useless if each bucket has one peer.
+                               try {
+                                       $this->statusCache = ObjectCache::newAccelerator( array() );
+                               } catch ( MWException $e ) {
+                                       trigger_error( __CLASS__ .
+                                               " using multiple DB peers without apc, xcache, or wincache." );
+                               }
+                               break;
+                       }
+               }
+
+               $this->session = wfRandomString( 31 );
+       }
+
+       /**
+        * Get a connection to a lock DB and acquire locks on $paths.
+        * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
+        *
+        * @see QuorumLockManager::getLocksOnServer()
+        * @return Status
+        */
+       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+
+               if ( $type == self::LOCK_EX ) { // writer locks
+                       try {
+                               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+                               # Build up values for INSERT clause
+                               $data = array();
+                               foreach ( $keys as $key ) {
+                                       $data[] = array( 'fle_key' => $key );
+                               }
+                               # Wait on any existing writers and block new ones if we get in
+                               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+                               $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
+                       } catch ( DBError $e ) {
+                               foreach ( $paths as $path ) {
+                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                               }
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::freeLocksOnServer()
+        * @return Status
+        */
+       protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
+               return Status::newGood(); // not supported
+       }
+
+       /**
+        * @see QuorumLockManager::releaseAllLocks()
+        * @return Status
+        */
+       protected function releaseAllLocks() {
+               $status = Status::newGood();
+
+               foreach ( $this->conns as $lockDb => $db ) {
+                       if ( $db->trxLevel() ) { // in transaction
+                               try {
+                                       $db->rollback( __METHOD__ ); // finish transaction and kill any rows
+                               } catch ( DBError $e ) {
+                                       $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+                               }
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::isServerUp()
+        * @return bool
+        */
+       protected function isServerUp( $lockSrv ) {
+               if ( !$this->cacheCheckFailures( $lockSrv ) ) {
+                       return false; // recent failure to connect
+               }
+               try {
+                       $this->getConnection( $lockSrv );
+               } catch ( DBError $e ) {
+                       $this->cacheRecordFailure( $lockSrv );
+                       return false; // failed to connect
+               }
+               return true;
+       }
+
+       /**
+        * Get (or reuse) a connection to a lock DB
+        *
+        * @param $lockDb string
+        * @return DatabaseBase
+        * @throws DBError
+        */
+       protected function getConnection( $lockDb ) {
+               if ( !isset( $this->conns[$lockDb] ) ) {
+                       $db = null;
+                       if ( $lockDb === 'localDBMaster' ) {
+                               $lb = wfGetLBFactory()->newMainLB();
+                               $db = $lb->getConnection( DB_MASTER );
+                       } elseif ( isset( $this->dbServers[$lockDb] ) ) {
+                               $config = $this->dbServers[$lockDb];
+                               $db = DatabaseBase::factory( $config['type'], $config );
+                       }
+                       if ( !$db ) {
+                               return null; // config error?
+                       }
+                       $this->conns[$lockDb] = $db;
+                       $this->conns[$lockDb]->clearFlag( DBO_TRX );
+                       # If the connection drops, try to avoid letting the DB rollback
+                       # and release the locks before the file operations are finished.
+                       # This won't handle the case of DB server restarts however.
+                       $options = array();
+                       if ( $this->lockExpiry > 0 ) {
+                               $options['connTimeout'] = $this->lockExpiry;
+                       }
+                       $this->conns[$lockDb]->setSessionOptions( $options );
+                       $this->initConnection( $lockDb, $this->conns[$lockDb] );
+               }
+               if ( !$this->conns[$lockDb]->trxLevel() ) {
+                       $this->conns[$lockDb]->begin( __METHOD__ ); // start transaction
+               }
+               return $this->conns[$lockDb];
+       }
+
+       /**
+        * Do additional initialization for new lock DB connection
+        *
+        * @param $lockDb string
+        * @param $db DatabaseBase
+        * @return void
+        * @throws DBError
+        */
+       protected function initConnection( $lockDb, DatabaseBase $db ) {}
+
+       /**
+        * Checks if the DB has not recently had connection/query errors.
+        * This just avoids wasting time on doomed connection attempts.
+        *
+        * @param $lockDb string
+        * @return bool
+        */
+       protected function cacheCheckFailures( $lockDb ) {
+               return ( $this->statusCache && $this->safeDelay > 0 )
+                       ? !$this->statusCache->get( $this->getMissKey( $lockDb ) )
+                       : true;
+       }
+
+       /**
+        * Log a lock request failure to the cache
+        *
+        * @param $lockDb string
+        * @return bool Success
+        */
+       protected function cacheRecordFailure( $lockDb ) {
+               return ( $this->statusCache && $this->safeDelay > 0 )
+                       ? $this->statusCache->set( $this->getMissKey( $lockDb ), 1, $this->safeDelay )
+                       : true;
+       }
+
+       /**
+        * Get a cache key for recent query misses for a DB
+        *
+        * @param $lockDb string
+        * @return string
+        */
+       protected function getMissKey( $lockDb ) {
+               $lockDb = ( $lockDb === 'localDBMaster' ) ? wfWikiID() : $lockDb; // non-relative
+               return 'dblockmanager:downservers:' . str_replace( ' ', '_', $lockDb );
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               foreach ( $this->conns as $db ) {
+                       if ( $db->trxLevel() ) { // in transaction
+                               try {
+                                       $db->rollback( __METHOD__ ); // finish transaction and kill any rows
+                               } catch ( DBError $e ) {
+                                       // oh well
+                               }
+                       }
+                       $db->close();
+               }
+       }
+}
+
+/**
+ * MySQL version of DBLockManager that supports shared locks.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class MySqlLockManager extends DBLockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       );
+
+       /**
+        * @param $lockDb string
+        * @param $db DatabaseBase
+        */
+       protected function initConnection( $lockDb, DatabaseBase $db ) {
+               # Let this transaction see lock rows from other transactions
+               $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
+       }
+
+       /**
+        * Get a connection to a lock DB and acquire locks on $paths.
+        * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
+        *
+        * @see DBLockManager::getLocksOnServer()
+        * @return Status
+        */
+       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+
+               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
+               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+               # Build up values for INSERT clause
+               $data = array();
+               foreach ( $keys as $key ) {
+                       $data[] = array( 'fls_key' => $key, 'fls_session' => $this->session );
+               }
+               # Block new writers...
+               $db->insert( 'filelocks_shared', $data, __METHOD__, array( 'IGNORE' ) );
+               # Actually do the locking queries...
+               if ( $type == self::LOCK_SH ) { // reader locks
+                       # Bail if there are any existing writers...
+                       $blocked = $db->selectField( 'filelocks_exclusive', '1',
+                               array( 'fle_key' => $keys ),
+                               __METHOD__
+                       );
+                       # Prospective writers that haven't yet updated filelocks_exclusive
+                       # will recheck filelocks_shared after doing so and bail due to our entry.
+               } else { // writer locks
+                       $encSession = $db->addQuotes( $this->session );
+                       # Bail if there are any existing writers...
+                       # The may detect readers, but the safe check for them is below.
+                       # Note: if two writers come at the same time, both bail :)
+                       $blocked = $db->selectField( 'filelocks_shared', '1',
+                               array( 'fls_key' => $keys, "fls_session != $encSession" ),
+                               __METHOD__
+                       );
+                       if ( !$blocked ) {
+                               # Build up values for INSERT clause
+                               $data = array();
+                               foreach ( $keys as $key ) {
+                                       $data[] = array( 'fle_key' => $key );
+                               }
+                               # Block new readers/writers...
+                               $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
+                               # Bail if there are any existing readers...
+                               $blocked = $db->selectField( 'filelocks_shared', '1',
+                                       array( 'fls_key' => $keys, "fls_session != $encSession" ),
+                                       __METHOD__
+                               );
+                       }
+               }
+
+               if ( $blocked ) {
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                       }
+               }
+
+               return $status;
+       }
+}
diff --git a/includes/filebackend/lockmanager/FSLockManager.php b/includes/filebackend/lockmanager/FSLockManager.php
new file mode 100644 (file)
index 0000000..53f3e9f
--- /dev/null
@@ -0,0 +1,255 @@
+<?php
+/**
+ * Simple version of LockManager based on using FS lock files.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup LockManager
+ */
+
+/**
+ * Simple version of LockManager based on using FS lock files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * This should work fine for small sites running off one server.
+ * Do not use this with 'lockDirectory' set to an NFS mount unless the
+ * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
+ * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class FSLockManager extends LockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       );
+
+       protected $lockDir; // global dir for all servers
+
+       /** @var Array Map of (locked key => lock type => lock file handle) */
+       protected $handles = array();
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * $config includes:
+        *     'lockDirectory' : Directory containing the lock files
+        *
+        * @param array $config
+        */
+       function __construct( array $config ) {
+               parent::__construct( $config );
+
+               $this->lockDir = $config['lockDirectory'];
+       }
+
+       /**
+        * @see LockManager::doLock()
+        * @param $paths array
+        * @param $type int
+        * @return Status
+        */
+       protected function doLock( array $paths, $type ) {
+               $status = Status::newGood();
+
+               $lockedPaths = array(); // files locked in this attempt
+               foreach ( $paths as $path ) {
+                       $status->merge( $this->doSingleLock( $path, $type ) );
+                       if ( $status->isOK() ) {
+                               $lockedPaths[] = $path;
+                       } else {
+                               // Abort and unlock everything
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+                               return $status;
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see LockManager::doUnlock()
+        * @param $paths array
+        * @param $type int
+        * @return Status
+        */
+       protected function doUnlock( array $paths, $type ) {
+               $status = Status::newGood();
+
+               foreach ( $paths as $path ) {
+                       $status->merge( $this->doSingleUnlock( $path, $type ) );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Lock a single resource key
+        *
+        * @param $path string
+        * @param $type integer
+        * @return Status
+        */
+       protected function doSingleLock( $path, $type ) {
+               $status = Status::newGood();
+
+               if ( isset( $this->locksHeld[$path][$type] ) ) {
+                       ++$this->locksHeld[$path][$type];
+               } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                       $this->locksHeld[$path][$type] = 1;
+               } else {
+                       wfSuppressWarnings();
+                       $handle = fopen( $this->getLockPath( $path ), 'a+' );
+                       wfRestoreWarnings();
+                       if ( !$handle ) { // lock dir missing?
+                               wfMkdirParents( $this->lockDir );
+                               $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
+                       }
+                       if ( $handle ) {
+                               // Either a shared or exclusive lock
+                               $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
+                               if ( flock( $handle, $lock | LOCK_NB ) ) {
+                                       // Record this lock as active
+                                       $this->locksHeld[$path][$type] = 1;
+                                       $this->handles[$path][$type] = $handle;
+                               } else {
+                                       fclose( $handle );
+                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                               }
+                       } else {
+                               $status->fatal( 'lockmanager-fail-openlock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * Unlock a single resource key
+        *
+        * @param $path string
+        * @param $type integer
+        * @return Status
+        */
+       protected function doSingleUnlock( $path, $type ) {
+               $status = Status::newGood();
+
+               if ( !isset( $this->locksHeld[$path] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
+               } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+                       $status->warning( 'lockmanager-notlocked', $path );
+               } else {
+                       $handlesToClose = array();
+                       --$this->locksHeld[$path][$type];
+                       if ( $this->locksHeld[$path][$type] <= 0 ) {
+                               unset( $this->locksHeld[$path][$type] );
+                               // If a LOCK_SH comes in while we have a LOCK_EX, we don't
+                               // actually add a handler, so check for handler existence.
+                               if ( isset( $this->handles[$path][$type] ) ) {
+                                       if ( $type === self::LOCK_EX
+                                               && isset( $this->locksHeld[$path][self::LOCK_SH] )
+                                               && !isset( $this->handles[$path][self::LOCK_SH] ) )
+                                       {
+                                               // EX lock came first: move this handle to the SH one
+                                               $this->handles[$path][self::LOCK_SH] = $this->handles[$path][$type];
+                                       } else {
+                                               // Mark this handle to be unlocked and closed
+                                               $handlesToClose[] = $this->handles[$path][$type];
+                                       }
+                                       unset( $this->handles[$path][$type] );
+                               }
+                       }
+                       if ( !count( $this->locksHeld[$path] ) ) {
+                               unset( $this->locksHeld[$path] ); // no locks on this path
+                       }
+                       // Unlock handles to release locks and delete
+                       // any lock files that end up with no locks on them...
+                       if ( wfIsWindows() ) {
+                               // Windows: for any process, including this one,
+                               // calling unlink() on a locked file will fail
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
+                       } else {
+                               // Unix: unlink() can be used on files currently open by this
+                               // process and we must do so in order to avoid race conditions
+                               $status->merge( $this->pruneKeyLockFiles( $path ) );
+                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @param $path string
+        * @param $handlesToClose array
+        * @return Status
+        */
+       private function closeLockHandles( $path, array $handlesToClose ) {
+               $status = Status::newGood();
+               foreach ( $handlesToClose as $handle ) {
+                       if ( !flock( $handle, LOCK_UN ) ) {
+                               $status->fatal( 'lockmanager-fail-releaselock', $path );
+                       }
+                       if ( !fclose( $handle ) ) {
+                               $status->warning( 'lockmanager-fail-closelock', $path );
+                       }
+               }
+               return $status;
+       }
+
+       /**
+        * @param $path string
+        * @return Status
+        */
+       private function pruneKeyLockFiles( $path ) {
+               $status = Status::newGood();
+               if ( !isset( $this->locksHeld[$path] ) ) {
+                       # No locks are held for the lock file anymore
+                       if ( !unlink( $this->getLockPath( $path ) ) ) {
+                               $status->warning( 'lockmanager-fail-deletelock', $path );
+                       }
+                       unset( $this->handles[$path] );
+               }
+               return $status;
+       }
+
+       /**
+        * Get the path to the lock file for a key
+        * @param $path string
+        * @return string
+        */
+       protected function getLockPath( $path ) {
+               $hash = self::sha1Base36( $path );
+               return "{$this->lockDir}/{$hash}.lock";
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               while ( count( $this->locksHeld ) ) {
+                       foreach ( $this->locksHeld as $path => $locks ) {
+                               $this->doSingleUnlock( $path, self::LOCK_EX );
+                               $this->doSingleUnlock( $path, self::LOCK_SH );
+                       }
+               }
+       }
+}
diff --git a/includes/filebackend/lockmanager/LSLockManager.php b/includes/filebackend/lockmanager/LSLockManager.php
new file mode 100644 (file)
index 0000000..8942818
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Version of LockManager based on using lock daemon servers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup LockManager
+ */
+
+/**
+ * Manage locks using a lock daemon server.
+ *
+ * Version of LockManager based on using lock daemon servers.
+ * This is meant for multi-wiki systems that may share files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * All lock requests for a resource, identified by a hash string, will map
+ * to one bucket. Each bucket maps to one or several peer servers, each
+ * running LockServerDaemon.php, listening on a designated TCP port.
+ * A majority of peers must agree for a lock to be acquired.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class LSLockManager extends QuorumLockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       );
+
+       /** @var Array Map of server names to server config */
+       protected $lockServers; // (server name => server config array)
+
+       /** @var Array Map Server connections (server name => resource) */
+       protected $conns = array();
+
+       protected $connTimeout; // float number of seconds
+       protected $session = ''; // random SHA-1 string
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * $config paramaters include:
+        *   - lockServers  : Associative array of server names to configuration.
+        *                    Configuration is an associative array that includes:
+        *                      - host    : IP address/hostname
+        *                      - port    : TCP port
+        *                      - authKey : Secret string the lock server uses
+        *   - srvsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
+        *                    each having an odd-numbered list of server names (peers) as values.
+        *   - connTimeout  : Lock server connection attempt timeout. [optional]
+        *
+        * @param Array $config
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+
+               $this->lockServers = $config['lockServers'];
+               // Sanitize srvsByBucket config to prevent PHP errors
+               $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
+               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
+
+               if ( isset( $config['connTimeout'] ) ) {
+                       $this->connTimeout = $config['connTimeout'];
+               } else {
+                       $this->connTimeout = 3; // use some sane amount
+               }
+
+               $this->session = wfRandomString( 32 ); // 128 bits
+       }
+
+       /**
+        * @see QuorumLockManager::getLocksOnServer()
+        * @return Status
+        */
+       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+
+               // Send out the command and get the response...
+               $type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
+               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+               $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys );
+
+               if ( $response !== 'ACQUIRED' ) {
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::freeLocksOnServer()
+        * @return Status
+        */
+       protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+
+               // Send out the command and get the response...
+               $type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
+               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+               $response = $this->sendCommand( $lockSrv, 'RELEASE', $type, $keys );
+
+               if ( $response !== 'RELEASED' ) {
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-releaselock', $path );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::releaseAllLocks()
+        * @return Status
+        */
+       protected function releaseAllLocks() {
+               $status = Status::newGood();
+
+               foreach ( $this->conns as $lockSrv => $conn ) {
+                       $response = $this->sendCommand( $lockSrv, 'RELEASE_ALL', '', array() );
+                       if ( $response !== 'RELEASED_ALL' ) {
+                               $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::isServerUp()
+        * @return bool
+        */
+       protected function isServerUp( $lockSrv ) {
+               return (bool)$this->getConnection( $lockSrv );
+       }
+
+       /**
+        * Send a command and get back the response
+        *
+        * @param $lockSrv string
+        * @param $action string
+        * @param $type string
+        * @param $values Array
+        * @return string|bool
+        */
+       protected function sendCommand( $lockSrv, $action, $type, $values ) {
+               $conn = $this->getConnection( $lockSrv );
+               if ( !$conn ) {
+                       return false; // no connection
+               }
+               $authKey = $this->lockServers[$lockSrv]['authKey'];
+               // Build of the command as a flat string...
+               $values = implode( '|', $values );
+               $key = sha1( $this->session . $action . $type . $values . $authKey );
+               // Send out the command...
+               if ( fwrite( $conn, "{$this->session}:$key:$action:$type:$values\n" ) === false ) {
+                       return false;
+               }
+               // Get the response...
+               $response = fgets( $conn );
+               if ( $response === false ) {
+                       return false;
+               }
+               return trim( $response );
+       }
+
+       /**
+        * Get (or reuse) a connection to a lock server
+        *
+        * @param $lockSrv string
+        * @return resource
+        */
+       protected function getConnection( $lockSrv ) {
+               if ( !isset( $this->conns[$lockSrv] ) ) {
+                       $cfg = $this->lockServers[$lockSrv];
+                       wfSuppressWarnings();
+                       $errno = $errstr = '';
+                       $conn = fsockopen( $cfg['host'], $cfg['port'], $errno, $errstr, $this->connTimeout );
+                       wfRestoreWarnings();
+                       if ( $conn === false ) {
+                               return null;
+                       }
+                       $sec = floor( $this->connTimeout );
+                       $usec = floor( ( $this->connTimeout - floor( $this->connTimeout ) ) * 1e6 );
+                       stream_set_timeout( $conn, $sec, $usec );
+                       $this->conns[$lockSrv] = $conn;
+               }
+               return $this->conns[$lockSrv];
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               $this->releaseAllLocks();
+               foreach ( $this->conns as $conn ) {
+                       fclose( $conn );
+               }
+       }
+}
diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php
new file mode 100644 (file)
index 0000000..07853f8
--- /dev/null
@@ -0,0 +1,425 @@
+<?php
+/**
+ * @defgroup LockManager Lock management
+ * @ingroup FileBackend
+ */
+
+/**
+ * Resource locking handling.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * @brief Class for handling resource locking.
+ *
+ * Locks on resource keys can either be shared or exclusive.
+ *
+ * Implementations must keep track of what is locked by this proccess
+ * in-memory and support nested locking calls (using reference counting).
+ * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
+ * Locks should either be non-blocking or have low wait timeouts.
+ *
+ * Subclasses should avoid throwing exceptions at all costs.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+abstract class LockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
+               self::LOCK_EX => self::LOCK_EX
+       );
+
+       /** @var Array Map of (resource path => lock type => count) */
+       protected $locksHeld = array();
+
+       /* Lock types; stronger locks have higher values */
+       const LOCK_SH = 1; // shared lock (for reads)
+       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
+       const LOCK_EX = 3; // exclusive lock (for writes)
+
+       /**
+        * Construct a new instance from configuration
+        *
+        * @param $config Array
+        */
+       public function __construct( array $config ) {}
+
+       /**
+        * Lock the resources at the given abstract paths
+        *
+        * @param $paths Array List of resource names
+        * @param $type integer LockManager::LOCK_* constant
+        * @return Status
+        */
+       final public function lock( array $paths, $type = self::LOCK_EX ) {
+               wfProfileIn( __METHOD__ );
+               $status = $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * Unlock the resources at the given abstract paths
+        *
+        * @param $paths Array List of storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @return Status
+        */
+       final public function unlock( array $paths, $type = self::LOCK_EX ) {
+               wfProfileIn( __METHOD__ );
+               $status = $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] );
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * Get the base 36 SHA-1 of a string, padded to 31 digits
+        *
+        * @param $path string
+        * @return string
+        */
+       final protected static function sha1Base36( $path ) {
+               return wfBaseConvert( sha1( $path ), 16, 36, 31 );
+       }
+
+       /**
+        * Lock resources with the given keys and lock type
+        *
+        * @param $paths Array List of storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @return string
+        */
+       abstract protected function doLock( array $paths, $type );
+
+       /**
+        * Unlock resources with the given keys and lock type
+        *
+        * @param $paths Array List of storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @return string
+        */
+       abstract protected function doUnlock( array $paths, $type );
+}
+
+/**
+ * Self-releasing locks
+ *
+ * LockManager helper class to handle scoped locks, which
+ * release when an object is destroyed or goes out of scope.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class ScopedLock {
+       /** @var LockManager */
+       protected $manager;
+       /** @var Status */
+       protected $status;
+       /** @var Array List of resource paths*/
+       protected $paths;
+
+       protected $type; // integer lock type
+
+       /**
+        * @param $manager LockManager
+        * @param $paths Array List of storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @param $status Status
+        */
+       protected function __construct(
+               LockManager $manager, array $paths, $type, Status $status
+       ) {
+               $this->manager = $manager;
+               $this->paths = $paths;
+               $this->status = $status;
+               $this->type = $type;
+       }
+
+       /**
+        * Get a ScopedLock object representing a lock on resource paths.
+        * Any locks are released once this object goes out of scope.
+        * The status object is updated with any errors or warnings.
+        *
+        * @param $manager LockManager
+        * @param $paths Array List of storage paths
+        * @param $type integer LockManager::LOCK_* constant
+        * @param $status Status
+        * @return ScopedLock|null Returns null on failure
+        */
+       public static function factory(
+               LockManager $manager, array $paths, $type, Status $status
+       ) {
+               $lockStatus = $manager->lock( $paths, $type );
+               $status->merge( $lockStatus );
+               if ( $lockStatus->isOK() ) {
+                       return new self( $manager, $paths, $type, $status );
+               }
+               return null;
+       }
+
+       function __destruct() {
+               $wasOk = $this->status->isOK();
+               $this->status->merge( $this->manager->unlock( $this->paths, $this->type ) );
+               if ( $wasOk ) {
+                       // Make sure status is OK, despite any unlockFiles() fatals
+                       $this->status->setResult( true, $this->status->value );
+               }
+       }
+}
+
+/**
+ * Version of LockManager that uses a quorum from peer servers for locks.
+ * The resource space can also be sharded into separate peer groups.
+ *
+ * @ingroup LockManager
+ * @since 1.20
+ */
+abstract class QuorumLockManager extends LockManager {
+       /** @var Array Map of bucket indexes to peer server lists */
+       protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...))
+
+       /**
+        * @see LockManager::doLock()
+        * @param $paths array
+        * @param $type int
+        * @return Status
+        */
+       final protected function doLock( array $paths, $type ) {
+               $status = Status::newGood();
+
+               $pathsToLock = array(); // (bucket => paths)
+               // Get locks that need to be acquired (buckets => locks)...
+               foreach ( $paths as $path ) {
+                       if ( isset( $this->locksHeld[$path][$type] ) ) {
+                               ++$this->locksHeld[$path][$type];
+                       } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+                               $this->locksHeld[$path][$type] = 1;
+                       } else {
+                               $bucket = $this->getBucketFromKey( $path );
+                               $pathsToLock[$bucket][] = $path;
+                       }
+               }
+
+               $lockedPaths = array(); // files locked in this attempt
+               // Attempt to acquire these locks...
+               foreach ( $pathsToLock as $bucket => $paths ) {
+                       // Try to acquire the locks for this bucket
+                       $status->merge( $this->doLockingRequestBucket( $bucket, $paths, $type ) );
+                       if ( !$status->isOK() ) {
+                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+                               return $status;
+                       }
+                       // Record these locks as active
+                       foreach ( $paths as $path ) {
+                               $this->locksHeld[$path][$type] = 1; // locked
+                       }
+                       // Keep track of what locks were made in this attempt
+                       $lockedPaths = array_merge( $lockedPaths, $paths );
+               }
+
+               return $status;
+       }
+
+       /**
+        * @see LockManager::doUnlock()
+        * @param $paths array
+        * @param $type int
+        * @return Status
+        */
+       final protected function doUnlock( array $paths, $type ) {
+               $status = Status::newGood();
+
+               $pathsToUnlock = array();
+               foreach ( $paths as $path ) {
+                       if ( !isset( $this->locksHeld[$path][$type] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $path );
+                       } else {
+                               --$this->locksHeld[$path][$type];
+                               // Reference count the locks held and release locks when zero
+                               if ( $this->locksHeld[$path][$type] <= 0 ) {
+                                       unset( $this->locksHeld[$path][$type] );
+                                       $bucket = $this->getBucketFromKey( $path );
+                                       $pathsToUnlock[$bucket][] = $path;
+                               }
+                               if ( !count( $this->locksHeld[$path] ) ) {
+                                       unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
+                               }
+                       }
+               }
+
+               // Remove these specific locks if possible, or at least release
+               // all locks once this process is currently not holding any locks.
+               foreach ( $pathsToUnlock as $bucket => $paths ) {
+                       $status->merge( $this->doUnlockingRequestBucket( $bucket, $paths, $type ) );
+               }
+               if ( !count( $this->locksHeld ) ) {
+                       $status->merge( $this->releaseAllLocks() );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Attempt to acquire locks with the peers for a bucket.
+        * This is all or nothing; if any key is locked then this totally fails.
+        *
+        * @param $bucket integer
+        * @param $paths Array List of resource keys to lock
+        * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+        * @return Status
+        */
+       final protected function doLockingRequestBucket( $bucket, array $paths, $type ) {
+               $status = Status::newGood();
+
+               $yesVotes = 0; // locks made on trustable servers
+               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+               $quorum = floor( $votesLeft/2 + 1 ); // simple majority
+               // Get votes for each peer, in order, until we have enough...
+               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+                       if ( !$this->isServerUp( $lockSrv ) ) {
+                               --$votesLeft;
+                               $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
+                               continue; // server down?
+                       }
+                       // Attempt to acquire the lock on this peer
+                       $status->merge( $this->getLocksOnServer( $lockSrv, $paths, $type ) );
+                       if ( !$status->isOK() ) {
+                               return $status; // vetoed; resource locked
+                       }
+                       ++$yesVotes; // success for this peer
+                       if ( $yesVotes >= $quorum ) {
+                               return $status; // lock obtained
+                       }
+                       --$votesLeft;
+                       $votesNeeded = $quorum - $yesVotes;
+                       if ( $votesNeeded > $votesLeft ) {
+                               break; // short-circuit
+                       }
+               }
+               // At this point, we must not have met the quorum
+               $status->setResult( false );
+
+               return $status;
+       }
+
+       /**
+        * Attempt to release locks with the peers for a bucket
+        *
+        * @param $bucket integer
+        * @param $paths Array List of resource keys to lock
+        * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+        * @return Status
+        */
+       final protected function doUnlockingRequestBucket( $bucket, array $paths, $type ) {
+               $status = Status::newGood();
+
+               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+                       if ( !$this->isServerUp( $lockSrv ) ) {
+                               $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
+                       // Attempt to release the lock on this peer
+                       } else {
+                               $status->merge( $this->freeLocksOnServer( $lockSrv, $paths, $type ) );
+                       }
+               }
+
+               return $status;
+       }
+
+       /**
+        * Get the bucket for resource path.
+        * This should avoid throwing any exceptions.
+        *
+        * @param $path string
+        * @return integer
+        */
+       protected function getBucketFromKey( $path ) {
+               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
+               return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
+       }
+
+       /**
+        * Check if a lock server is up
+        *
+        * @param $lockSrv string
+        * @return bool
+        */
+       abstract protected function isServerUp( $lockSrv );
+
+       /**
+        * Get a connection to a lock server and acquire locks on $paths
+        *
+        * @param $lockSrv string
+        * @param $paths array
+        * @param $type integer
+        * @return Status
+        */
+       abstract protected function getLocksOnServer( $lockSrv, array $paths, $type );
+
+       /**
+        * Get a connection to a lock server and release locks on $paths.
+        *
+        * Subclasses must effectively implement this or releaseAllLocks().
+        *
+        * @param $lockSrv string
+        * @param $paths array
+        * @param $type integer
+        * @return Status
+        */
+       abstract protected function freeLocksOnServer( $lockSrv, array $paths, $type );
+
+       /**
+        * Release all locks that this session is holding.
+        *
+        * Subclasses must effectively implement this or freeLocksOnServer().
+        *
+        * @return Status
+        */
+       abstract protected function releaseAllLocks();
+}
+
+/**
+ * Simple version of LockManager that does nothing
+ * @since 1.19
+ */
+class NullLockManager extends LockManager {
+       /**
+        * @see LockManager::doLock()
+        * @param $paths array
+        * @param $type int
+        * @return Status
+        */
+       protected function doLock( array $paths, $type ) {
+               return Status::newGood();
+       }
+
+       /**
+        * @see LockManager::doUnlock()
+        * @param $paths array
+        * @param $type int
+        * @return Status
+        */
+       protected function doUnlock( array $paths, $type ) {
+               return Status::newGood();
+       }
+}
diff --git a/includes/filebackend/lockmanager/LockManagerGroup.php b/includes/filebackend/lockmanager/LockManagerGroup.php
new file mode 100644 (file)
index 0000000..8c8c940
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Lock manager registration handling.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup LockManager
+ */
+
+/**
+ * Class to handle file lock manager registration
+ *
+ * @ingroup LockManager
+ * @author Aaron Schulz
+ * @since 1.19
+ */
+class LockManagerGroup {
+       /**
+        * @var LockManagerGroup
+        */
+       protected static $instance = null;
+
+       /** @var Array of (name => ('class' =>, 'config' =>, 'instance' =>)) */
+       protected $managers = array();
+
+       protected function __construct() {}
+
+       /**
+        * @return LockManagerGroup
+        */
+       public static function singleton() {
+               if ( self::$instance == null ) {
+                       self::$instance = new self();
+                       self::$instance->initFromGlobals();
+               }
+               return self::$instance;
+       }
+
+       /**
+        * Destroy the singleton instance, so that a new one will be created next
+        * time singleton() is called.
+        */
+       public static function destroySingleton() {
+               self::$instance = null;
+       }
+
+       /**
+        * Register lock managers from the global variables
+        *
+        * @return void
+        */
+       protected function initFromGlobals() {
+               global $wgLockManagers;
+
+               $this->register( $wgLockManagers );
+       }
+
+       /**
+        * Register an array of file lock manager configurations
+        *
+        * @param $configs Array
+        * @return void
+        * @throws MWException
+        */
+       protected function register( array $configs ) {
+               foreach ( $configs as $config ) {
+                       if ( !isset( $config['name'] ) ) {
+                               throw new MWException( "Cannot register a lock manager with no name." );
+                       }
+                       $name = $config['name'];
+                       if ( !isset( $config['class'] ) ) {
+                               throw new MWException( "Cannot register lock manager `{$name}` with no class." );
+                       }
+                       $class = $config['class'];
+                       unset( $config['class'] ); // lock manager won't need this
+                       $this->managers[$name] = array(
+                               'class'    => $class,
+                               'config'   => $config,
+                               'instance' => null
+                       );
+               }
+       }
+
+       /**
+        * Get the lock manager object with a given name
+        *
+        * @param $name string
+        * @return LockManager
+        * @throws MWException
+        */
+       public function get( $name ) {
+               if ( !isset( $this->managers[$name] ) ) {
+                       throw new MWException( "No lock manager defined with the name `$name`." );
+               }
+               // Lazy-load the actual lock manager instance
+               if ( !isset( $this->managers[$name]['instance'] ) ) {
+                       $class = $this->managers[$name]['class'];
+                       $config = $this->managers[$name]['config'];
+                       $this->managers[$name]['instance'] = new $class( $config );
+               }
+               return $this->managers[$name]['instance'];
+       }
+
+       /**
+        * Get the default lock manager configured for the site.
+        * Returns NullLockManager if no lock manager could be found.
+        *
+        * @return LockManager
+        */
+       public function getDefault() {
+               return isset( $this->managers['default'] )
+                       ? $this->get( 'default' )
+                       : new NullLockManager( array() );
+       }
+
+       /**
+        * Get the default lock manager configured for the site
+        * or at least some other effective configured lock manager.
+        * Throws an exception if no lock manager could be found.
+        *
+        * @return LockManager
+        * @throws MWException
+        */
+       public function getAny() {
+               return isset( $this->managers['default'] )
+                       ? $this->get( 'default' )
+                       : $this->get( 'fsLockManager' );
+       }
+}
diff --git a/includes/filebackend/lockmanager/MemcLockManager.php b/includes/filebackend/lockmanager/MemcLockManager.php
new file mode 100644 (file)
index 0000000..9e81dbf
--- /dev/null
@@ -0,0 +1,319 @@
+<?php
+/**
+ * Version of LockManager based on using memcached servers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup LockManager
+ */
+
+/**
+ * Manage locks using memcached servers.
+ *
+ * Version of LockManager based on using memcached servers.
+ * This is meant for multi-wiki systems that may share files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * All lock requests for a resource, identified by a hash string, will map
+ * to one bucket. Each bucket maps to one or several peer servers, each running memcached.
+ * A majority of peers must agree for a lock to be acquired.
+ *
+ * @ingroup LockManager
+ * @since 1.20
+ */
+class MemcLockManager extends QuorumLockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       );
+
+       /** @var Array Map server names to MemcachedBagOStuff objects */
+       protected $bagOStuffs = array();
+       /** @var Array */
+       protected $serversUp = array(); // (server name => bool)
+
+       protected $lockExpiry; // integer; maximum time locks can be held
+       protected $session = ''; // string; random SHA-1 UUID
+       protected $wikiId = ''; // string
+
+       /**
+        * Construct a new instance from configuration.
+        *
+        * $config paramaters include:
+        *   - 'lockServers'  : Associative array of server names to "<IP>:<port>" strings.
+        *   - 'srvsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
+        *                      each having an odd-numbered list of server names (peers) as values.
+        *   - 'memcConfig'   : Configuration array for ObjectCache::newFromParams. [optional]
+        *                      If set, this must use one of the memcached classes.
+        *   - 'wikiId'       : Wiki ID string that all resources are relative to. [optional]
+        *
+        * @param Array $config
+        */
+       public function __construct( array $config ) {
+               parent::__construct( $config );
+
+               // Sanitize srvsByBucket config to prevent PHP errors
+               $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
+               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
+
+               $memcConfig = isset( $config['memcConfig'] )
+                       ? $config['memcConfig']
+                       : array( 'class' => 'MemcachedPhpBagOStuff' );
+
+               foreach ( $config['lockServers'] as $name => $address ) {
+                       $params = array( 'servers' => array( $address ) ) + $memcConfig;
+                       $cache = ObjectCache::newFromParams( $params );
+                       if ( $cache instanceof MemcachedBagOStuff ) {
+                               $this->bagOStuffs[$name] = $cache;
+                       } else {
+                               throw new MWException(
+                                       'Only MemcachedBagOStuff classes are supported by MemcLockManager.' );
+                       }
+               }
+
+               $this->wikiId = isset( $config['wikiId'] ) ? $config['wikiId'] : wfWikiID();
+
+               $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
+               $this->lockExpiry = $met ? 2*(int)$met : 2*3600;
+
+               $this->session = wfRandomString( 32 );
+       }
+
+       /**
+        * @see QuorumLockManager::getLocksOnServer()
+        * @return Status
+        */
+       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+
+               $memc = $this->getCache( $lockSrv );
+               $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
+
+               // Lock all of the active lock record keys...
+               if ( !$this->acquireMutexes( $memc, $keys ) ) {
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                       }
+                       return;
+               }
+
+               // Fetch all the existing lock records...
+               $lockRecords = $memc->getMulti( $keys );
+
+               $now = time();
+               // Check if the requested locks conflict with existing ones...
+               foreach ( $paths as $path ) {
+                       $locksKey = $this->recordKeyForPath( $path );
+                       $locksHeld = isset( $lockRecords[$locksKey] )
+                               ? $lockRecords[$locksKey]
+                               : array( self::LOCK_SH => array(), self::LOCK_EX => array() ); // init
+                       foreach ( $locksHeld[self::LOCK_EX] as $session => $expiry ) {
+                               if ( $expiry < $now ) { // stale?
+                                       unset( $locksHeld[self::LOCK_EX][$session] );
+                               } elseif ( $session !== $this->session ) {
+                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                               }
+                       }
+                       if ( $type === self::LOCK_EX ) {
+                               foreach ( $locksHeld[self::LOCK_SH] as $session => $expiry ) {
+                                       if ( $expiry < $now ) { // stale?
+                                               unset( $locksHeld[self::LOCK_SH][$session] );
+                                       } elseif ( $session !== $this->session ) {
+                                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
+                                       }
+                               }
+                       }
+                       if ( $status->isOK() ) {
+                               // Register the session in the lock record array
+                               $locksHeld[$type][$this->session] = $now + $this->lockExpiry;
+                               // We will update this record if none of the other locks conflict
+                               $lockRecords[$locksKey] = $locksHeld;
+                       }
+               }
+
+               // If there were no lock conflicts, update all the lock records...
+               if ( $status->isOK() ) {
+                       foreach ( $lockRecords as $locksKey => $locksHeld ) {
+                               $memc->set( $locksKey, $locksHeld );
+                               wfDebug( __METHOD__ . ": acquired lock on key $locksKey.\n" );
+                       }
+               }
+
+               // Unlock all of the active lock record keys...
+               $this->releaseMutexes( $memc, $keys );
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::freeLocksOnServer()
+        * @return Status
+        */
+       protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
+               $status = Status::newGood();
+
+               $memc = $this->getCache( $lockSrv );
+               $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
+
+               // Lock all of the active lock record keys...
+               if ( !$this->acquireMutexes( $memc, $keys ) ) {
+                       foreach ( $paths as $path ) {
+                               $status->fatal( 'lockmanager-fail-releaselock', $path );
+                       }
+                       return;
+               }
+
+               // Fetch all the existing lock records...
+               $lockRecords = $memc->getMulti( $keys );
+
+               // Remove the requested locks from all records...
+               foreach ( $paths as $path ) {
+                       $locksKey = $this->recordKeyForPath( $path ); // lock record
+                       if ( !isset( $lockRecords[$locksKey] ) ) {
+                               continue; // nothing to do
+                       }
+                       $locksHeld = $lockRecords[$locksKey];
+                       if ( is_array( $locksHeld ) && isset( $locksHeld[$type] ) ) {
+                               unset( $locksHeld[$type][$this->session] );
+                               $ok = $memc->set( $locksKey, $locksHeld );
+                       } else {
+                               $ok = true;
+                       }
+                       if ( !$ok ) {
+                               $status->fatal( 'lockmanager-fail-releaselock', $path );
+                       }
+                       wfDebug( __METHOD__ . ": released lock on key $locksKey.\n" );
+               }
+
+               // Unlock all of the active lock record keys...
+               $this->releaseMutexes( $memc, $keys );
+
+               return $status;
+       }
+
+       /**
+        * @see QuorumLockManager::releaseAllLocks()
+        * @return Status
+        */
+       protected function releaseAllLocks() {
+               return Status::newGood(); // not supported
+       }
+
+       /**
+        * @see QuorumLockManager::isServerUp()
+        * @return bool
+        */
+       protected function isServerUp( $lockSrv ) {
+               return (bool)$this->getCache( $lockSrv );
+       }
+
+       /**
+        * Get the MemcachedBagOStuff object for a $lockSrv
+        *
+        * @param $lockSrv string Server name
+        * @return MemcachedBagOStuff|null
+        */
+       protected function getCache( $lockSrv ) {
+               $memc = null;
+               if ( isset( $this->bagOStuffs[$lockSrv] ) ) {
+                       $memc = $this->bagOStuffs[$lockSrv];
+                       if ( !isset( $this->serversUp[$lockSrv] ) ) {
+                               $this->serversUp[$lockSrv] = $memc->set( 'MemcLockManager:ping', 1, 1 );
+                               if ( !$this->serversUp[$lockSrv] ) {
+                                       trigger_error( __METHOD__ . ": Could not contact $lockSrv.", E_USER_WARNING );
+                               }
+                       }
+                       if ( !$this->serversUp[$lockSrv] ) {
+                               return null; // server appears to be down
+                       }
+               }
+               return $memc;
+       }
+
+       /**
+        * @param $path string
+        * @return string
+        */
+       protected function recordKeyForPath( $path ) {
+               $hash = LockManager::sha1Base36( $path );
+               list( $db, $prefix ) = wfSplitWikiID( $this->wikiId );
+               return wfForeignMemcKey( $db, $prefix, __CLASS__, 'locks', $hash );
+       }
+
+       /**
+        * @param $memc MemcachedBagOStuff
+        * @param $keys Array List of keys to acquire
+        * @return bool
+        */
+       protected function acquireMutexes( MemcachedBagOStuff $memc, array $keys ) {
+               $lockedKeys = array();
+
+               // Acquire the keys in lexicographical order, to avoid deadlock problems.
+               // If P1 is waiting to acquire a key P2 has, P2 can't also be waiting for a key P1 has.
+               sort( $keys );
+
+               // Try to quickly loop to acquire the keys, but back off after a few rounds.
+               // This reduces memcached spam, especially in the rare case where a server acquires
+               // some lock keys and dies without releasing them. Lock keys expire after a few minutes.
+               $rounds = 0;
+               $start = microtime( true );
+               do {
+                       if ( ( ++$rounds % 4 ) == 0 ) {
+                               usleep( 1000*50 ); // 50 ms
+                       }
+                       foreach ( array_diff( $keys, $lockedKeys ) as $key ) {
+                               if ( $memc->add( "$key:mutex", 1, 180 ) ) { // lock record
+                                       $lockedKeys[] = $key;
+                               } else {
+                                       continue; // acquire in order
+                               }
+                       }
+               } while ( count( $lockedKeys ) < count( $keys ) && ( microtime( true ) - $start ) <= 6 );
+
+               if ( count( $lockedKeys ) != count( $keys ) ) {
+                       $this->releaseMutexes( $lockedKeys ); // failed; release what was locked
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * @param $memc MemcachedBagOStuff
+        * @param $keys Array List of acquired keys
+        * @return void
+        */
+       protected function releaseMutexes( MemcachedBagOStuff $memc, array $keys ) {
+               foreach ( $keys as $key ) {
+                       $memc->delete( "$key:mutex" );
+               }
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               while ( count( $this->locksHeld ) ) {
+                       foreach ( $this->locksHeld as $path => $locks ) {
+                               $this->doUnlock( array( $path ), self::LOCK_EX );
+                               $this->doUnlock( array( $path ), self::LOCK_SH );
+                       }
+               }
+       }
+}
diff --git a/includes/filerepo/backend/FSFile.php b/includes/filerepo/backend/FSFile.php
deleted file mode 100644 (file)
index e07c99d..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-<?php
-/**
- * Non-directory file on the file system.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- */
-
-/**
- * Class representing a non-directory file on the file system
- *
- * @ingroup FileBackend
- */
-class FSFile {
-       protected $path; // path to file
-
-       /**
-        * Sets up the file object
-        *
-        * @param $path string Path to temporary file on local disk
-        * @throws MWException
-        */
-       public function __construct( $path ) {
-               if ( FileBackend::isStoragePath( $path ) ) {
-                       throw new MWException( __METHOD__ . " given storage path `$path`." );
-               }
-               $this->path = $path;
-       }
-
-       /**
-        * Returns the file system path
-        *
-        * @return String
-        */
-       public function getPath() {
-               return $this->path;
-       }
-
-       /**
-        * Checks if the file exists
-        *
-        * @return bool
-        */
-       public function exists() {
-               return is_file( $this->path );
-       }
-
-       /**
-        * Get the file size in bytes
-        *
-        * @return int|bool
-        */
-       public function getSize() {
-               return filesize( $this->path );
-       }
-
-       /**
-        * Get the file's last-modified timestamp
-        *
-        * @return string|bool TS_MW timestamp or false on failure
-        */
-       public function getTimestamp() {
-               wfSuppressWarnings();
-               $timestamp = filemtime( $this->path );
-               wfRestoreWarnings();
-               if ( $timestamp !== false ) {
-                       $timestamp = wfTimestamp( TS_MW, $timestamp );
-               }
-               return $timestamp;
-       }
-
-       /**
-        * Guess the MIME type from the file contents alone
-        * 
-        * @return string 
-        */
-       public function getMimeType() {
-               return MimeMagic::singleton()->guessMimeType( $this->path, false );
-       }
-
-       /**
-        * Get an associative array containing information about
-        * a file with the given storage path.
-        *
-        * @param $ext Mixed: the file extension, or true to extract it from the filename.
-        *             Set it to false to ignore the extension.
-        *
-        * @return array
-        */
-       public function getProps( $ext = true ) {
-               wfProfileIn( __METHOD__ );
-               wfDebug( __METHOD__.": Getting file info for $this->path\n" );
-
-               $info = self::placeholderProps();
-               $info['fileExists'] = $this->exists();
-
-               if ( $info['fileExists'] ) {
-                       $magic = MimeMagic::singleton();
-
-                       # get the file extension
-                       if ( $ext === true ) {
-                               $ext = self::extensionFromPath( $this->path );
-                       }
-
-                       # mime type according to file contents
-                       $info['file-mime'] = $this->getMimeType();
-                       # logical mime type
-                       $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
-
-                       list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
-                       $info['media_type'] = $magic->getMediaType( $this->path, $info['mime'] );
-
-                       # Get size in bytes
-                       $info['size'] = $this->getSize();
-
-                       # Height, width and metadata
-                       $handler = MediaHandler::getHandler( $info['mime'] );
-                       if ( $handler ) {
-                               $tempImage = (object)array();
-                               $info['metadata'] = $handler->getMetadata( $tempImage, $this->path );
-                               $gis = $handler->getImageSize( $tempImage, $this->path, $info['metadata'] );
-                               if ( is_array( $gis ) ) {
-                                       $info = $this->extractImageSizeInfo( $gis ) + $info;
-                               }
-                       }
-                       $info['sha1'] = $this->getSha1Base36();
-
-                       wfDebug(__METHOD__.": $this->path loaded, {$info['size']} bytes, {$info['mime']}.\n");
-               } else {
-                       wfDebug(__METHOD__.": $this->path NOT FOUND!\n");
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $info;
-       }
-
-       /**
-        * Placeholder file properties to use for files that don't exist
-        *
-        * @return Array
-        */
-       public static function placeholderProps() {
-               $info = array();
-               $info['fileExists'] = false;
-               $info['mime'] = null;
-               $info['media_type'] = MEDIATYPE_UNKNOWN;
-               $info['metadata'] = '';
-               $info['sha1'] = '';
-               $info['width'] = 0;
-               $info['height'] = 0;
-               $info['bits'] = 0;
-               return $info;
-       }
-
-       /**
-        * Exract image size information
-        *
-        * @param $gis array
-        * @return Array
-        */
-       protected function extractImageSizeInfo( array $gis ) {
-               $info = array();
-               # NOTE: $gis[2] contains a code for the image type. This is no longer used.
-               $info['width'] = $gis[0];
-               $info['height'] = $gis[1];
-               if ( isset( $gis['bits'] ) ) {
-                       $info['bits'] = $gis['bits'];
-               } else {
-                       $info['bits'] = 0;
-               }
-               return $info;
-       }
-
-       /**
-        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
-        * encoding, zero padded to 31 digits.
-        *
-        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
-        * fairly neatly.
-        *
-        * @return bool|string False on failure
-        */
-       public function getSha1Base36() {
-               wfProfileIn( __METHOD__ );
-
-               wfSuppressWarnings();
-               $hash = sha1_file( $this->path );
-               wfRestoreWarnings();
-               if ( $hash !== false ) {
-                       $hash = wfBaseConvert( $hash, 16, 36, 31 );
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $hash;
-       }
-
-       /**
-        * Get the final file extension from a file system path
-        * 
-        * @param $path string
-        * @return string
-        */
-       public static function extensionFromPath( $path ) {
-               $i = strrpos( $path, '.' );
-               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
-       }
-
-       /**
-        * Get an associative array containing information about a file in the local filesystem.
-        *
-        * @param $path String: absolute local filesystem path
-        * @param $ext Mixed: the file extension, or true to extract it from the filename.
-        *             Set it to false to ignore the extension.
-        *
-        * @return array
-        */
-       public static function getPropsFromPath( $path, $ext = true ) {
-               $fsFile = new self( $path );
-               return $fsFile->getProps( $ext );
-       }
-
-       /**
-        * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
-        * encoding, zero padded to 31 digits.
-        *
-        * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
-        * fairly neatly.
-        *
-        * @param $path string
-        *
-        * @return bool|string False on failure
-        */
-       public static function getSha1Base36FromPath( $path ) {
-               $fsFile = new self( $path );
-               return $fsFile->getSha1Base36();
-       }
-}
diff --git a/includes/filerepo/backend/FSFileBackend.php b/includes/filerepo/backend/FSFileBackend.php
deleted file mode 100644 (file)
index c953d77..0000000
+++ /dev/null
@@ -1,976 +0,0 @@
-<?php
-/**
- * File system based backend.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * @brief Class for a file system (FS) based file backend.
- *
- * All "containers" each map to a directory under the backend's base directory.
- * For backwards-compatibility, some container paths can be set to custom paths.
- * The wiki ID will not be used in any custom paths, so this should be avoided.
- *
- * Having directories with thousands of files will diminish performance.
- * Sharding can be accomplished by using FileRepo-style hash paths.
- *
- * Status messages should avoid mentioning the internal FS paths.
- * PHP warnings are assumed to be logged rather than output.
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-class FSFileBackend extends FileBackendStore {
-       protected $basePath; // string; directory holding the container directories
-       /** @var Array Map of container names to root paths */
-       protected $containerPaths = array(); // for custom container paths
-       protected $fileMode; // integer; file permission mode
-
-       protected $hadWarningErrors = array();
-
-       /**
-        * @see FileBackendStore::__construct()
-        * Additional $config params include:
-        *   - basePath       : File system directory that holds containers.
-        *   - containerPaths : Map of container names to custom file system directories.
-        *                      This should only be used for backwards-compatibility.
-        *   - fileMode       : Octal UNIX file permissions to use on files stored.
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-
-               // Remove any possible trailing slash from directories
-               if ( isset( $config['basePath'] ) ) {
-                       $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
-               } else {
-                       $this->basePath = null; // none; containers must have explicit paths
-               }
-
-               if ( isset( $config['containerPaths'] ) ) {
-                       $this->containerPaths = (array)$config['containerPaths'];
-                       foreach ( $this->containerPaths as &$path ) {
-                               $path = rtrim( $path, '/' );  // remove trailing slash
-                       }
-               }
-
-               $this->fileMode = isset( $config['fileMode'] )
-                       ? $config['fileMode']
-                       : 0644;
-       }
-
-       /**
-        * @see FileBackendStore::resolveContainerPath()
-        * @param $container string
-        * @param $relStoragePath string
-        * @return null|string
-        */
-       protected function resolveContainerPath( $container, $relStoragePath ) {
-               // Check that container has a root directory
-               if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
-                       // Check for sane relative paths (assume the base paths are OK)
-                       if ( $this->isLegalRelPath( $relStoragePath ) ) {
-                               return $relStoragePath;
-                       }
-               }
-               return null;
-       }
-
-       /**
-        * Sanity check a relative file system path for validity
-        *
-        * @param $path string Normalized relative path
-        * @return bool
-        */
-       protected function isLegalRelPath( $path ) {
-               // Check for file names longer than 255 chars
-               if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
-                       return false;
-               }
-               if ( wfIsWindows() ) { // NTFS
-                       return !preg_match( '![:*?"<>|]!', $path );
-               } else {
-                       return true;
-               }
-       }
-
-       /**
-        * Given the short (unresolved) and full (resolved) name of
-        * a container, return the file system path of the container.
-        *
-        * @param $shortCont string
-        * @param $fullCont string
-        * @return string|null
-        */
-       protected function containerFSRoot( $shortCont, $fullCont ) {
-               if ( isset( $this->containerPaths[$shortCont] ) ) {
-                       return $this->containerPaths[$shortCont];
-               } elseif ( isset( $this->basePath ) ) {
-                       return "{$this->basePath}/{$fullCont}";
-               }
-               return null; // no container base path defined
-       }
-
-       /**
-        * Get the absolute file system path for a storage path
-        *
-        * @param $storagePath string Storage path
-        * @return string|null
-        */
-       protected function resolveToFSPath( $storagePath ) {
-               list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
-               if ( $relPath === null ) {
-                       return null; // invalid
-               }
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $storagePath );
-               $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               if ( $relPath != '' ) {
-                       $fsPath .= "/{$relPath}";
-               }
-               return $fsPath;
-       }
-
-       /**
-        * @see FileBackendStore::isPathUsableInternal()
-        * @return bool
-        */
-       public function isPathUsableInternal( $storagePath ) {
-               $fsPath = $this->resolveToFSPath( $storagePath );
-               if ( $fsPath === null ) {
-                       return false; // invalid
-               }
-               $parentDir = dirname( $fsPath );
-
-               if ( file_exists( $fsPath ) ) {
-                       $ok = is_file( $fsPath ) && is_writable( $fsPath );
-               } else {
-                       $ok = is_dir( $parentDir ) && is_writable( $parentDir );
-               }
-
-               return $ok;
-       }
-
-       /**
-        * @see FileBackendStore::doStoreInternal()
-        * @return Status
-        */
-       protected function doStoreInternal( array $params ) {
-               $status = Status::newGood();
-
-               $dest = $this->resolveToFSPath( $params['dst'] );
-               if ( $dest === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               if ( file_exists( $dest ) ) {
-                       if ( !empty( $params['overwrite'] ) ) {
-                               $ok = unlink( $dest );
-                               if ( !$ok ) {
-                                       $status->fatal( 'backend-fail-delete', $params['dst'] );
-                                       return $status;
-                               }
-                       } else {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               }
-
-               if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
-                               wfEscapeShellArg( $this->cleanPathSlashes( $params['src'] ) ),
-                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
-                       ) );
-                       $status->value = new FSFileOpHandle( $this, $params, 'Store', $cmd, $dest );
-               } else { // immediate write
-                       $ok = copy( $params['src'], $dest );
-                       // In some cases (at least over NFS), copy() returns true when it fails
-                       if ( !$ok || ( filesize( $params['src'] ) !== filesize( $dest ) ) ) {
-                               if ( $ok ) { // PHP bug
-                                       unlink( $dest ); // remove broken file
-                                       trigger_error( __METHOD__ . ": copy() failed but returned true." );
-                               }
-                               $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
-                               return $status;
-                       }
-                       $this->chmod( $dest );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FSFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseStore( $errors, Status $status, array $params, $cmd ) {
-               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
-                       $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
-                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doCopyInternal()
-        * @return Status
-        */
-       protected function doCopyInternal( array $params ) {
-               $status = Status::newGood();
-
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
-                       return $status;
-               }
-
-               $dest = $this->resolveToFSPath( $params['dst'] );
-               if ( $dest === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               if ( file_exists( $dest ) ) {
-                       if ( !empty( $params['overwrite'] ) ) {
-                               $ok = unlink( $dest );
-                               if ( !$ok ) {
-                                       $status->fatal( 'backend-fail-delete', $params['dst'] );
-                                       return $status;
-                               }
-                       } else {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               }
-
-               if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
-                               wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
-                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
-                       ) );
-                       $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd, $dest );
-               } else { // immediate write
-                       $ok = copy( $source, $dest );
-                       // In some cases (at least over NFS), copy() returns true when it fails
-                       if ( !$ok || ( filesize( $source ) !== filesize( $dest ) ) ) {
-                               if ( $ok ) { // PHP bug
-                                       unlink( $dest ); // remove broken file
-                                       trigger_error( __METHOD__ . ": copy() failed but returned true." );
-                               }
-                               $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-                               return $status;
-                       }
-                       $this->chmod( $dest );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FSFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseCopy( $errors, Status $status, array $params, $cmd ) {
-               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doMoveInternal()
-        * @return Status
-        */
-       protected function doMoveInternal( array $params ) {
-               $status = Status::newGood();
-
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
-                       return $status;
-               }
-
-               $dest = $this->resolveToFSPath( $params['dst'] );
-               if ( $dest === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               if ( file_exists( $dest ) ) {
-                       if ( !empty( $params['overwrite'] ) ) {
-                               // Windows does not support moving over existing files
-                               if ( wfIsWindows() ) {
-                                       $ok = unlink( $dest );
-                                       if ( !$ok ) {
-                                               $status->fatal( 'backend-fail-delete', $params['dst'] );
-                                               return $status;
-                                       }
-                               }
-                       } else {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               }
-
-               if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', array( wfIsWindows() ? 'MOVE' : 'mv',
-                               wfEscapeShellArg( $this->cleanPathSlashes( $source ) ),
-                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
-                       ) );
-                       $status->value = new FSFileOpHandle( $this, $params, 'Move', $cmd );
-               } else { // immediate write
-                       $ok = rename( $source, $dest );
-                       clearstatcache(); // file no longer at source
-                       if ( !$ok ) {
-                               $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
-                               return $status;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FSFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseMove( $errors, Status $status, array $params, $cmd ) {
-               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
-                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
-                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doDeleteInternal()
-        * @return Status
-        */
-       protected function doDeleteInternal( array $params ) {
-               $status = Status::newGood();
-
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
-                       return $status;
-               }
-
-               if ( !is_file( $source ) ) {
-                       if ( empty( $params['ignoreMissingSource'] ) ) {
-                               $status->fatal( 'backend-fail-delete', $params['src'] );
-                       }
-                       return $status; // do nothing; either OK or bad status
-               }
-
-               if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', array( wfIsWindows() ? 'DEL' : 'unlink',
-                               wfEscapeShellArg( $this->cleanPathSlashes( $source ) )
-                       ) );
-                       $status->value = new FSFileOpHandle( $this, $params, 'Copy', $cmd );
-               } else { // immediate write
-                       $ok = unlink( $source );
-                       if ( !$ok ) {
-                               $status->fatal( 'backend-fail-delete', $params['src'] );
-                               return $status;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FSFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseDelete( $errors, Status $status, array $params, $cmd ) {
-               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
-                       $status->fatal( 'backend-fail-delete', $params['src'] );
-                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doCreateInternal()
-        * @return Status
-        */
-       protected function doCreateInternal( array $params ) {
-               $status = Status::newGood();
-
-               $dest = $this->resolveToFSPath( $params['dst'] );
-               if ( $dest === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               if ( file_exists( $dest ) ) {
-                       if ( !empty( $params['overwrite'] ) ) {
-                               $ok = unlink( $dest );
-                               if ( !$ok ) {
-                                       $status->fatal( 'backend-fail-delete', $params['dst'] );
-                                       return $status;
-                               }
-                       } else {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               }
-
-               if ( !empty( $params['async'] ) ) { // deferred
-                       $tempFile = TempFSFile::factory( 'create_', 'tmp' );
-                       if ( !$tempFile ) {
-                               $status->fatal( 'backend-fail-create', $params['dst'] );
-                               return $status;
-                       }
-                       $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
-                       if ( $bytes === false ) {
-                               $status->fatal( 'backend-fail-create', $params['dst'] );
-                               return $status;
-                       }
-                       $cmd = implode( ' ', array( wfIsWindows() ? 'COPY' : 'cp',
-                               wfEscapeShellArg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
-                               wfEscapeShellArg( $this->cleanPathSlashes( $dest ) )
-                       ) );
-                       $status->value = new FSFileOpHandle( $this, $params, 'Create', $cmd, $dest );
-                       $tempFile->bind( $status->value );
-               } else { // immediate write
-                       $bytes = file_put_contents( $dest, $params['content'] );
-                       if ( $bytes === false ) {
-                               $status->fatal( 'backend-fail-create', $params['dst'] );
-                               return $status;
-                       }
-                       $this->chmod( $dest );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FSFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseCreate( $errors, Status $status, array $params, $cmd ) {
-               if ( $errors !== '' && !( wfIsWindows() && $errors[0] === " " ) ) {
-                       $status->fatal( 'backend-fail-create', $params['dst'] );
-                       trigger_error( "$cmd\n$errors", E_USER_WARNING ); // command output
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doPrepareInternal()
-        * @return Status
-        */
-       protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
-               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               $existed = is_dir( $dir ); // already there?
-               if ( !wfMkdirParents( $dir ) ) { // make directory and its parents
-                       $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
-               } elseif ( !is_writable( $dir ) ) {
-                       $status->fatal( 'directoryreadonlyerror', $params['dir'] );
-               } elseif ( !is_readable( $dir ) ) {
-                       $status->fatal( 'directorynotreadableerror', $params['dir'] );
-               }
-               if ( is_dir( $dir ) && !$existed ) {
-                       // Respect any 'noAccess' or 'noListing' flags...
-                       $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
-               }
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doSecureInternal()
-        * @return Status
-        */
-       protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
-               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               // Seed new directories with a blank index.html, to prevent crawling...
-               if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
-                       $bytes = file_put_contents( "{$dir}/index.html", $this->indexHtmlPrivate() );
-                       if ( $bytes === false ) {
-                               $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
-                               return $status;
-                       }
-               }
-               // Add a .htaccess file to the root of the container...
-               if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
-                       $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
-                       if ( $bytes === false ) {
-                               $storeDir = "mwstore://{$this->name}/{$shortCont}";
-                               $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
-                               return $status;
-                       }
-               }
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doPublishInternal()
-        * @return Status
-        */
-       protected function doPublishInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
-               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               // Unseed new directories with a blank index.html, to allow crawling...
-               if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
-                       $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
-                       if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
-                               $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
-                               return $status;
-                       }
-               }
-               // Remove the .htaccess file from the root of the container...
-               if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
-                       $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
-                       if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
-                               $storeDir = "mwstore://{$this->name}/{$shortCont}";
-                               $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
-                               return $status;
-                       }
-               }
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doCleanInternal()
-        * @return Status
-        */
-       protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
-               $status = Status::newGood();
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
-               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               wfSuppressWarnings();
-               if ( is_dir( $dir ) ) {
-                       rmdir( $dir ); // remove directory if empty
-               }
-               wfRestoreWarnings();
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doFileExists()
-        * @return array|bool|null
-        */
-       protected function doGetFileStat( array $params ) {
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
-                       return false; // invalid storage path
-               }
-
-               $this->trapWarnings(); // don't trust 'false' if there were errors
-               $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
-               $hadError = $this->untrapWarnings();
-
-               if ( $stat ) {
-                       return array(
-                               'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
-                               'size'  => $stat['size']
-                       );
-               } elseif ( !$hadError ) {
-                       return false; // file does not exist
-               } else {
-                       return null; // failure
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doClearCache()
-        */
-       protected function doClearCache( array $paths = null ) {
-               clearstatcache(); // clear the PHP file stat cache
-       }
-
-       /**
-        * @see FileBackendStore::doDirectoryExists()
-        * @return bool|null
-        */
-       protected function doDirectoryExists( $fullCont, $dirRel, array $params ) {
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
-               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-
-               $this->trapWarnings(); // don't trust 'false' if there were errors
-               $exists = is_dir( $dir );
-               $hadError = $this->untrapWarnings();
-
-               return $hadError ? null : $exists;
-       }
-
-       /**
-        * @see FileBackendStore::getDirectoryListInternal()
-        * @return Array|null
-        */
-       public function getDirectoryListInternal( $fullCont, $dirRel, array $params ) {
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
-               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               $exists = is_dir( $dir );
-               if ( !$exists ) {
-                       wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
-                       return array(); // nothing under this dir
-               } elseif ( !is_readable( $dir ) ) {
-                       wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
-                       return null; // bad permissions?
-               }
-               return new FSFileBackendDirList( $dir, $params );
-       }
-
-       /**
-        * @see FileBackendStore::getFileListInternal()
-        * @return array|FSFileBackendFileList|null
-        */
-       public function getFileListInternal( $fullCont, $dirRel, array $params ) {
-               list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
-               $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
-               $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               $exists = is_dir( $dir );
-               if ( !$exists ) {
-                       wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
-                       return array(); // nothing under this dir
-               } elseif ( !is_readable( $dir ) ) {
-                       wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
-                       return null; // bad permissions?
-               }
-               return new FSFileBackendFileList( $dir, $params );
-       }
-
-       /**
-        * @see FileBackendStore::getLocalReference()
-        * @return FSFile|null
-        */
-       public function getLocalReference( array $params ) {
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
-                       return null;
-               }
-               return new FSFile( $source );
-       }
-
-       /**
-        * @see FileBackendStore::getLocalCopy()
-        * @return null|TempFSFile
-        */
-       public function getLocalCopy( array $params ) {
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
-                       return null;
-               }
-
-               // Create a new temporary file with the same extension...
-               $ext = FileBackend::extensionFromPath( $params['src'] );
-               $tmpFile = TempFSFile::factory( wfBaseName( $source ) . '_', $ext );
-               if ( !$tmpFile ) {
-                       return null;
-               }
-               $tmpPath = $tmpFile->getPath();
-
-               // Copy the source file over the temp file
-               $ok = copy( $source, $tmpPath );
-               if ( !$ok ) {
-                       return null;
-               }
-
-               $this->chmod( $tmpPath );
-
-               return $tmpFile;
-       }
-
-       /**
-        * @see FileBackendStore::directoriesAreVirtual()
-        * @return bool
-        */
-       protected function directoriesAreVirtual() {
-               return false;
-       }
-
-       /**
-        * @see FileBackendStore::doExecuteOpHandlesInternal()
-        * @return Array List of corresponding Status objects
-        */
-       protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
-               $statuses = array();
-
-               $pipes = array();
-               foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                       $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
-               }
-
-               $errs = array();
-               foreach ( $pipes as $index => $pipe ) {
-                       // Result will be empty on success in *NIX. On Windows,
-                       // it may be something like "        1 file(s) [copied|moved].".
-                       $errs[$index] = stream_get_contents( $pipe );
-                       fclose( $pipe );
-               }
-
-               foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                       $status = Status::newGood();
-                       $function = '_getResponse' . $fileOpHandle->call;
-                       $this->$function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
-                       $statuses[$index] = $status;
-                       if ( $status->isOK() && $fileOpHandle->chmodPath ) {
-                               $this->chmod( $fileOpHandle->chmodPath );
-                       }
-               }
-
-               clearstatcache(); // files changed
-               return $statuses;
-       }
-
-       /**
-        * Chmod a file, suppressing the warnings
-        *
-        * @param $path string Absolute file system path
-        * @return bool Success
-        */
-       protected function chmod( $path ) {
-               wfSuppressWarnings();
-               $ok = chmod( $path, $this->fileMode );
-               wfRestoreWarnings();
-
-               return $ok;
-       }
-
-       /**
-        * Return the text of an index.html file to hide directory listings
-        *
-        * @return string
-        */
-       protected function indexHtmlPrivate() {
-               return '';
-       }
-
-       /**
-        * Return the text of a .htaccess file to make a directory private
-        *
-        * @return string
-        */
-       protected function htaccessPrivate() {
-               return "Deny from all\n";
-       }
-
-       /**
-        * Clean up directory separators for the given OS
-        *
-        * @param $path string FS path
-        * @return string
-        */
-       protected function cleanPathSlashes( $path ) {
-               return wfIsWindows() ? strtr( $path, '/', '\\' ) : $path;
-       }
-
-       /**
-        * Listen for E_WARNING errors and track whether any happen
-        *
-        * @return bool
-        */
-       protected function trapWarnings() {
-               $this->hadWarningErrors[] = false; // push to stack
-               set_error_handler( array( $this, 'handleWarning' ), E_WARNING );
-               return false; // invoke normal PHP error handler
-       }
-
-       /**
-        * Stop listening for E_WARNING errors and return true if any happened
-        *
-        * @return bool
-        */
-       protected function untrapWarnings() {
-               restore_error_handler(); // restore previous handler
-               return array_pop( $this->hadWarningErrors ); // pop from stack
-       }
-
-       /**
-        * @return bool
-        */
-       private function handleWarning() {
-               $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
-               return true; // suppress from PHP handler
-       }
-}
-
-/**
- * @see FileBackendStoreOpHandle
- */
-class FSFileOpHandle extends FileBackendStoreOpHandle {
-       public $cmd; // string; shell command
-       public $chmodPath; // string; file to chmod
-
-       /**
-        * @param $backend
-        * @param $params array
-        * @param $call
-        * @param $cmd
-        * @param $chmodPath null
-        */
-       public function __construct( $backend, array $params, $call, $cmd, $chmodPath = null ) {
-               $this->backend = $backend;
-               $this->params = $params;
-               $this->call = $call;
-               $this->cmd = $cmd;
-               $this->chmodPath = $chmodPath;
-       }
-}
-
-/**
- * Wrapper around RecursiveDirectoryIterator/DirectoryIterator that
- * catches exception or does any custom behavoir that we may want.
- * Do not use this class from places outside FSFileBackend.
- *
- * @ingroup FileBackend
- */
-abstract class FSFileBackendList implements Iterator {
-       /** @var Iterator */
-       protected $iter;
-       protected $suffixStart; // integer
-       protected $pos = 0; // integer
-       /** @var Array */
-       protected $params = array();
-
-       /**
-        * @param $dir string file system directory
-        * @param $params array
-        */
-       public function __construct( $dir, array $params ) {
-               $dir = realpath( $dir ); // normalize
-               $this->suffixStart = strlen( $dir ) + 1; // size of "path/to/dir/"
-               $this->params = $params;
-
-               try {
-                       $this->iter = $this->initIterator( $dir );
-               } catch ( UnexpectedValueException $e ) {
-                       $this->iter = null; // bad permissions? deleted?
-               }
-       }
-
-       /**
-        * Return an appropriate iterator object to wrap
-        *
-        * @param $dir string file system directory
-        * @return Iterator
-        */
-       protected function initIterator( $dir ) {
-               if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
-                       # Get an iterator that will get direct sub-nodes
-                       return new DirectoryIterator( $dir );
-               } else { // recursive
-                       # Get an iterator that will return leaf nodes (non-directories)
-                       # RecursiveDirectoryIterator extends FilesystemIterator.
-                       # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
-                       $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
-                       return new RecursiveIteratorIterator(
-                               new RecursiveDirectoryIterator( $dir, $flags ),
-                               RecursiveIteratorIterator::CHILD_FIRST // include dirs
-                       );
-               }
-       }
-
-       /**
-        * @see Iterator::key()
-        * @return integer
-        */
-       public function key() {
-               return $this->pos;
-       }
-
-       /**
-        * @see Iterator::current()
-        * @return string|bool String or false
-        */
-       public function current() {
-               return $this->getRelPath( $this->iter->current()->getPathname() );
-       }
-
-       /**
-        * @see Iterator::next()
-        * @return void
-        */
-       public function next() {
-               try {
-                       $this->iter->next();
-                       $this->filterViaNext();
-               } catch ( UnexpectedValueException $e ) {
-                       $this->iter = null;
-               }
-               ++$this->pos;
-       }
-
-       /**
-        * @see Iterator::rewind()
-        * @return void
-        */
-       public function rewind() {
-               $this->pos = 0;
-               try {
-                       $this->iter->rewind();
-                       $this->filterViaNext();
-               } catch ( UnexpectedValueException $e ) {
-                       $this->iter = null;
-               }
-       }
-
-       /**
-        * @see Iterator::valid()
-        * @return bool
-        */
-       public function valid() {
-               return $this->iter && $this->iter->valid();
-       }
-
-       /**
-        * Filter out items by advancing to the next ones
-        */
-       protected function filterViaNext() {}
-
-       /**
-        * Return only the relative path and normalize slashes to FileBackend-style.
-        * Uses the "real path" since the suffix is based upon that.
-        *
-        * @param $path string
-        * @return string
-        */
-       protected function getRelPath( $path ) {
-               return strtr( substr( realpath( $path ), $this->suffixStart ), '\\', '/' );
-       }
-}
-
-class FSFileBackendDirList extends FSFileBackendList {
-       protected function filterViaNext() {
-               while ( $this->iter->valid() ) {
-                       if ( $this->iter->current()->isDot() || !$this->iter->current()->isDir() ) {
-                               $this->iter->next(); // skip non-directories and dot files
-                       } else {
-                               break;
-                       }
-               }
-       }
-}
-
-class FSFileBackendFileList extends FSFileBackendList {
-       protected function filterViaNext() {
-               while ( $this->iter->valid() ) {
-                       if ( !$this->iter->current()->isFile() ) {
-                               $this->iter->next(); // skip non-files and dot files
-                       } else {
-                               break;
-                       }
-               }
-       }
-}
diff --git a/includes/filerepo/backend/FileBackend.php b/includes/filerepo/backend/FileBackend.php
deleted file mode 100644 (file)
index ff9329c..0000000
+++ /dev/null
@@ -1,1112 +0,0 @@
-<?php
-/**
- * @defgroup FileBackend File backend
- * @ingroup  FileRepo
- *
- * File backend is used to interact with file storage systems,
- * such as the local file system, NFS, or cloud storage systems.
- */
-
-/**
- * Base class for all file backends.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * @brief Base class for all file backend classes (including multi-write backends).
- *
- * This class defines the methods as abstract that subclasses must implement.
- * Outside callers can assume that all backends will have these functions.
- *
- * All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
- * The "<path>" portion is a relative path that uses UNIX file system (FS)
- * notation, though any particular backend may not actually be using a local
- * filesystem.
- * Therefore, the relative paths are only virtual.
- *
- * Backend contents are stored under wiki-specific container names by default.
- * For legacy reasons, this has no effect for the FS backend class, and per-wiki
- * segregation must be done by setting the container paths appropriately.
- *
- * FS-based backends are somewhat more restrictive due to the existence of real
- * directory files; a regular file cannot have the same name as a directory. Other
- * backends with virtual directories may not have this limitation. Callers should
- * store files in such a way that no files and directories are under the same path.
- *
- * Methods should avoid throwing exceptions at all costs.
- * As a corollary, external dependencies should be kept to a minimum.
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-abstract class FileBackend {
-       protected $name; // string; unique backend name
-       protected $wikiId; // string; unique wiki name
-       protected $readOnly; // string; read-only explanation message
-       protected $parallelize; // string; when to do operations in parallel
-       protected $concurrency; // integer; how many operations can be done in parallel
-
-       /** @var LockManager */
-       protected $lockManager;
-       /** @var FileJournal */
-       protected $fileJournal;
-
-       /**
-        * Create a new backend instance from configuration.
-        * This should only be called from within FileBackendGroup.
-        *
-        * $config includes:
-        *   - name        : The unique name of this backend.
-        *                   This should consist of alphanumberic, '-', and '_' characters.
-        *                   This name should not be changed after use.
-        *   - wikiId      : Prefix to container names that is unique to this wiki.
-        *                   It should only consist of alphanumberic, '-', and '_' characters.
-        *   - lockManager : Registered name of a file lock manager to use.
-        *   - fileJournal : File journal configuration; see FileJournal::factory().
-        *                   Journals simply log changes to files stored in the backend.
-        *   - readOnly    : Write operations are disallowed if this is a non-empty string.
-        *                   It should be an explanation for the backend being read-only.
-        *   - parallelize : When to do file operations in parallel (when possible).
-        *                   Allowed values are "implicit", "explicit" and "off".
-        *   - concurrency : How many file operations can be done in parallel.
-        *
-        * @param $config Array
-        * @throws MWException
-        */
-       public function __construct( array $config ) {
-               $this->name = $config['name'];
-               if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
-                       throw new MWException( "Backend name `{$this->name}` is invalid." );
-               }
-               $this->wikiId = isset( $config['wikiId'] )
-                       ? $config['wikiId']
-                       : wfWikiID(); // e.g. "my_wiki-en_"
-               $this->lockManager = ( $config['lockManager'] instanceof LockManager )
-                       ? $config['lockManager']
-                       : LockManagerGroup::singleton()->get( $config['lockManager'] );
-               $this->fileJournal = isset( $config['fileJournal'] )
-                       ? ( ( $config['fileJournal'] instanceof FileJournal )
-                               ? $config['fileJournal']
-                               : FileJournal::factory( $config['fileJournal'], $this->name ) )
-                       : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
-               $this->readOnly = isset( $config['readOnly'] )
-                       ? (string)$config['readOnly']
-                       : '';
-               $this->parallelize = isset( $config['parallelize'] )
-                       ? (string)$config['parallelize']
-                       : 'off';
-               $this->concurrency = isset( $config['concurrency'] )
-                       ? (int)$config['concurrency']
-                       : 50;
-       }
-
-       /**
-        * Get the unique backend name.
-        * We may have multiple different backends of the same type.
-        * For example, we can have two Swift backends using different proxies.
-        *
-        * @return string
-        */
-       final public function getName() {
-               return $this->name;
-       }
-
-       /**
-        * Check if this backend is read-only
-        *
-        * @return bool
-        */
-       final public function isReadOnly() {
-               return ( $this->readOnly != '' );
-       }
-
-       /**
-        * Get an explanatory message if this backend is read-only
-        *
-        * @return string|bool Returns false if the backend is not read-only
-        */
-       final public function getReadOnlyReason() {
-               return ( $this->readOnly != '' ) ? $this->readOnly : false;
-       }
-
-       /**
-        * This is the main entry point into the backend for write operations.
-        * Callers supply an ordered list of operations to perform as a transaction.
-        * Files will be locked, the stat cache cleared, and then the operations attempted.
-        * If any serious errors occur, all attempted operations will be rolled back.
-        *
-        * $ops is an array of arrays. The outer array holds a list of operations.
-        * Each inner array is a set of key value pairs that specify an operation.
-        *
-        * Supported operations and their parameters. The supported actions are:
-        *  - create
-        *  - store
-        *  - copy
-        *  - move
-        *  - delete
-        *  - null
-        *
-        * a) Create a new file in storage with the contents of a string
-        * @code
-        *     array(
-        *         'op'                  => 'create',
-        *         'dst'                 => <storage path>,
-        *         'content'             => <string of new file contents>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>
-        *     );
-        * @endcode
-        *
-        * b) Copy a file system file into storage
-        * @code
-        *     array(
-        *         'op'                  => 'store',
-        *         'src'                 => <file system path>,
-        *         'dst'                 => <storage path>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>
-        *     )
-        * @endcode
-        *
-        * c) Copy a file within storage
-        * @code
-        *     array(
-        *         'op'                  => 'copy',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>
-        *     )
-        * @endcode
-        *
-        * d) Move a file within storage
-        * @code
-        *     array(
-        *         'op'                  => 'move',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>,
-        *         'overwrite'           => <boolean>,
-        *         'overwriteSame'       => <boolean>
-        *     )
-        * @endcode
-        *
-        * e) Delete a file within storage
-        * @code
-        *     array(
-        *         'op'                  => 'delete',
-        *         'src'                 => <storage path>,
-        *         'ignoreMissingSource' => <boolean>
-        *     )
-        * @endcode
-        *
-        * f) Do nothing (no-op)
-        * @code
-        *     array(
-        *         'op'                  => 'null',
-        *     )
-        * @endcode
-        *
-        * Boolean flags for operations (operation-specific):
-        *   - ignoreMissingSource : The operation will simply succeed and do
-        *                           nothing if the source file does not exist.
-        *   - overwrite           : Any destination file will be overwritten.
-        *   - overwriteSame       : An error will not be given if a file already
-        *                           exists at the destination that has the same
-        *                           contents as the new contents to be written there.
-        *
-        * $opts is an associative of boolean flags, including:
-        *   - force               : Operation precondition errors no longer trigger an abort.
-        *                           Any remaining operations are still attempted. Unexpected
-        *                           failures may still cause remaning operations to be aborted.
-        *   - nonLocking          : No locks are acquired for the operations.
-        *                           This can increase performance for non-critical writes.
-        *                           This has no effect unless the 'force' flag is set.
-        *   - allowStale          : Don't require the latest available data.
-        *                           This can increase performance for non-critical writes.
-        *                           This has no effect unless the 'force' flag is set.
-        *   - nonJournaled        : Don't log this operation batch in the file journal.
-        *                           This limits the ability of recovery scripts.
-        *   - parallelize         : Try to do operations in parallel when possible.
-        *   - bypassReadOnly      : Allow writes in read-only mode (since 1.20).
-        *
-        * @remarks Remarks on locking:
-        * File system paths given to operations should refer to files that are
-        * already locked or otherwise safe from modification from other processes.
-        * Normally these files will be new temp files, which should be adequate.
-        *
-        * @par Return value:
-        *
-        * This returns a Status, which contains all warnings and fatals that occured
-        * during the operation. The 'failCount', 'successCount', and 'success' members
-        * will reflect each operation attempted.
-        *
-        * The status will be "OK" unless:
-        *   - a) unexpected operation errors occurred (network partitions, disk full...)
-        *   - b) significant operation errors occured and 'force' was not set
-        *
-        * @param $ops Array List of operations to execute in order
-        * @param $opts Array Batch operation options
-        * @return Status
-        */
-       final public function doOperations( array $ops, array $opts = array() ) {
-               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               if ( empty( $opts['force'] ) ) { // sanity
-                       unset( $opts['nonLocking'] );
-                       unset( $opts['allowStale'] );
-               }
-               $opts['concurrency'] = 1; // off
-               if ( $this->parallelize === 'implicit' ) {
-                       if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
-                               $opts['concurrency'] = $this->concurrency;
-                       }
-               } elseif ( $this->parallelize === 'explicit' ) {
-                       if ( !empty( $opts['parallelize'] ) ) {
-                               $opts['concurrency'] = $this->concurrency;
-                       }
-               }
-               return $this->doOperationsInternal( $ops, $opts );
-       }
-
-       /**
-        * @see FileBackend::doOperations()
-        */
-       abstract protected function doOperationsInternal( array $ops, array $opts );
-
-       /**
-        * Same as doOperations() except it takes a single operation.
-        * If you are doing a batch of operations that should either
-        * all succeed or all fail, then use that function instead.
-        *
-        * @see FileBackend::doOperations()
-        *
-        * @param $op Array Operation
-        * @param $opts Array Operation options
-        * @return Status
-        */
-       final public function doOperation( array $op, array $opts = array() ) {
-               return $this->doOperations( array( $op ), $opts );
-       }
-
-       /**
-        * Performs a single create operation.
-        * This sets $params['op'] to 'create' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param $params Array Operation parameters
-        * @param $opts Array Operation options
-        * @return Status
-        */
-       final public function create( array $params, array $opts = array() ) {
-               return $this->doOperation( array( 'op' => 'create' ) + $params, $opts );
-       }
-
-       /**
-        * Performs a single store operation.
-        * This sets $params['op'] to 'store' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param $params Array Operation parameters
-        * @param $opts Array Operation options
-        * @return Status
-        */
-       final public function store( array $params, array $opts = array() ) {
-               return $this->doOperation( array( 'op' => 'store' ) + $params, $opts );
-       }
-
-       /**
-        * Performs a single copy operation.
-        * This sets $params['op'] to 'copy' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param $params Array Operation parameters
-        * @param $opts Array Operation options
-        * @return Status
-        */
-       final public function copy( array $params, array $opts = array() ) {
-               return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts );
-       }
-
-       /**
-        * Performs a single move operation.
-        * This sets $params['op'] to 'move' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param $params Array Operation parameters
-        * @param $opts Array Operation options
-        * @return Status
-        */
-       final public function move( array $params, array $opts = array() ) {
-               return $this->doOperation( array( 'op' => 'move' ) + $params, $opts );
-       }
-
-       /**
-        * Performs a single delete operation.
-        * This sets $params['op'] to 'delete' and passes it to doOperation().
-        *
-        * @see FileBackend::doOperation()
-        *
-        * @param $params Array Operation parameters
-        * @param $opts Array Operation options
-        * @return Status
-        */
-       final public function delete( array $params, array $opts = array() ) {
-               return $this->doOperation( array( 'op' => 'delete' ) + $params, $opts );
-       }
-
-       /**
-        * Perform a set of independent file operations on some files.
-        *
-        * This does no locking, nor journaling, and possibly no stat calls.
-        * Any destination files that already exist will be overwritten.
-        * This should *only* be used on non-original files, like cache files.
-        *
-        * Supported operations and their parameters:
-        *  - create
-        *  - store
-        *  - copy
-        *  - move
-        *  - delete
-        *  - null
-        *
-        * a) Create a new file in storage with the contents of a string
-        * @code
-        *     array(
-        *         'op'                  => 'create',
-        *         'dst'                 => <storage path>,
-        *         'content'             => <string of new file contents>
-        *     )
-        * @endcode
-        * b) Copy a file system file into storage
-        * @code
-        *     array(
-        *         'op'                  => 'store',
-        *         'src'                 => <file system path>,
-        *         'dst'                 => <storage path>
-        *     )
-        * @endcode
-        * c) Copy a file within storage
-        * @code
-        *     array(
-        *         'op'                  => 'copy',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>
-        *     )
-        * @endcode
-        * d) Move a file within storage
-        * @code
-        *     array(
-        *         'op'                  => 'move',
-        *         'src'                 => <storage path>,
-        *         'dst'                 => <storage path>
-        *     )
-        * @endcode
-        * e) Delete a file within storage
-        * @code
-        *     array(
-        *         'op'                  => 'delete',
-        *         'src'                 => <storage path>,
-        *         'ignoreMissingSource' => <boolean>
-        *     )
-        * @endcode
-        * f) Do nothing (no-op)
-        * @code
-        *     array(
-        *         'op'                  => 'null',
-        *     )
-        * @endcode
-        *
-        * @par Boolean flags for operations (operation-specific):
-        *   - ignoreMissingSource : The operation will simply succeed and do
-        *                           nothing if the source file does not exist.
-        *
-        * $opts is an associative of boolean flags, including:
-        *   - bypassReadOnly      : Allow writes in read-only mode (since 1.20)
-        *
-        * @par Return value:
-        * This returns a Status, which contains all warnings and fatals that occured
-        * during the operation. The 'failCount', 'successCount', and 'success' members
-        * will reflect each operation attempted for the given files. The status will be
-        * considered "OK" as long as no fatal errors occured.
-        *
-        * @param $ops Array Set of operations to execute
-        * @param $opts Array Batch operation options
-        * @return Status
-        * @since 1.20
-        */
-       final public function doQuickOperations( array $ops, array $opts = array() ) {
-               if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               foreach ( $ops as &$op ) {
-                       $op['overwrite'] = true; // avoids RTTs in key/value stores
-               }
-               return $this->doQuickOperationsInternal( $ops );
-       }
-
-       /**
-        * @see FileBackend::doQuickOperations()
-        * @since 1.20
-        */
-       abstract protected function doQuickOperationsInternal( array $ops );
-
-       /**
-        * Same as doQuickOperations() except it takes a single operation.
-        * If you are doing a batch of operations, then use that function instead.
-        *
-        * @see FileBackend::doQuickOperations()
-        *
-        * @param $op Array Operation
-        * @return Status
-        * @since 1.20
-        */
-       final public function doQuickOperation( array $op ) {
-               return $this->doQuickOperations( array( $op ) );
-       }
-
-       /**
-        * Performs a single quick create operation.
-        * This sets $params['op'] to 'create' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param $params Array Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickCreate( array $params ) {
-               return $this->doQuickOperation( array( 'op' => 'create' ) + $params );
-       }
-
-       /**
-        * Performs a single quick store operation.
-        * This sets $params['op'] to 'store' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param $params Array Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickStore( array $params ) {
-               return $this->doQuickOperation( array( 'op' => 'store' ) + $params );
-       }
-
-       /**
-        * Performs a single quick copy operation.
-        * This sets $params['op'] to 'copy' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param $params Array Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickCopy( array $params ) {
-               return $this->doQuickOperation( array( 'op' => 'copy' ) + $params );
-       }
-
-       /**
-        * Performs a single quick move operation.
-        * This sets $params['op'] to 'move' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param $params Array Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickMove( array $params ) {
-               return $this->doQuickOperation( array( 'op' => 'move' ) + $params );
-       }
-
-       /**
-        * Performs a single quick delete operation.
-        * This sets $params['op'] to 'delete' and passes it to doQuickOperation().
-        *
-        * @see FileBackend::doQuickOperation()
-        *
-        * @param $params Array Operation parameters
-        * @return Status
-        * @since 1.20
-        */
-       final public function quickDelete( array $params ) {
-               return $this->doQuickOperation( array( 'op' => 'delete' ) + $params );
-       }
-
-       /**
-        * Concatenate a list of storage files into a single file system file.
-        * The target path should refer to a file that is already locked or
-        * otherwise safe from modification from other processes. Normally,
-        * the file will be a new temp file, which should be adequate.
-        *
-        * @param $params Array Operation parameters
-        * $params include:
-        *   - srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
-        *   - dst  : file system path to 0-byte temp file
-        * @return Status
-        */
-       abstract public function concatenate( array $params );
-
-       /**
-        * Prepare a storage directory for usage.
-        * This will create any required containers and parent directories.
-        * Backends using key/value stores only need to create the container.
-        *
-        * The 'noAccess' and 'noListing' parameters works the same as in secure(),
-        * except they are only applied *if* the directory/container had to be created.
-        * These flags should always be set for directories that have private files.
-        *
-        * @param $params Array
-        * $params include:
-        *   - dir            : storage directory
-        *   - noAccess       : try to deny file access (since 1.20)
-        *   - noListing      : try to deny file listing (since 1.20)
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        */
-       final public function prepare( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               return $this->doPrepare( $params );
-       }
-
-       /**
-        * @see FileBackend::prepare()
-        */
-       abstract protected function doPrepare( array $params );
-
-       /**
-        * Take measures to block web access to a storage directory and
-        * the container it belongs to. FS backends might add .htaccess
-        * files whereas key/value store backends might revoke container
-        * access to the storage user representing end-users in web requests.
-        * This is not guaranteed to actually do anything.
-        *
-        * @param $params Array
-        * $params include:
-        *   - dir            : storage directory
-        *   - noAccess       : try to deny file access
-        *   - noListing      : try to deny file listing
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        */
-       final public function secure( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               return $this->doSecure( $params );
-       }
-
-       /**
-        * @see FileBackend::secure()
-        */
-       abstract protected function doSecure( array $params );
-
-       /**
-        * Remove measures to block web access to a storage directory and
-        * the container it belongs to. FS backends might remove .htaccess
-        * files whereas key/value store backends might grant container
-        * access to the storage user representing end-users in web requests.
-        * This essentially can undo the result of secure() calls.
-        *
-        * @param $params Array
-        * $params include:
-        *   - dir            : storage directory
-        *   - access         : try to allow file access
-        *   - listing        : try to allow file listing
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        * @since 1.20
-        */
-       final public function publish( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               return $this->doPublish( $params );
-       }
-
-       /**
-        * @see FileBackend::publish()
-        */
-       abstract protected function doPublish( array $params );
-
-       /**
-        * Delete a storage directory if it is empty.
-        * Backends using key/value stores may do nothing unless the directory
-        * is that of an empty container, in which case it should be deleted.
-        *
-        * @param $params Array
-        * $params include:
-        *   - dir            : storage directory
-        *   - recursive      : recursively delete empty subdirectories first (since 1.20)
-        *   - bypassReadOnly : allow writes in read-only mode (since 1.20)
-        * @return Status
-        */
-       final public function clean( array $params ) {
-               if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
-                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
-               }
-               return $this->doClean( $params );
-       }
-
-       /**
-        * @see FileBackend::clean()
-        */
-       abstract protected function doClean( array $params );
-
-       /**
-        * Check if a file exists at a storage path in the backend.
-        * This returns false if only a directory exists at the path.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return bool|null Returns null on failure
-        */
-       abstract public function fileExists( array $params );
-
-       /**
-        * Get the last-modified timestamp of the file at a storage path.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return string|bool TS_MW timestamp or false on failure
-        */
-       abstract public function getFileTimestamp( array $params );
-
-       /**
-        * Get the contents of a file at a storage path in the backend.
-        * This should be avoided for potentially large files.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return string|bool Returns false on failure
-        */
-       abstract public function getFileContents( array $params );
-
-       /**
-        * Get the size (bytes) of a file at a storage path in the backend.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return integer|bool Returns false on failure
-        */
-       abstract public function getFileSize( array $params );
-
-       /**
-        * Get quick information about a file at a storage path in the backend.
-        * If the file does not exist, then this returns false.
-        * Otherwise, the result is an associative array that includes:
-        *   - mtime  : the last-modified timestamp (TS_MW)
-        *   - size   : the file size (bytes)
-        * Additional values may be included for internal use only.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return Array|bool|null Returns null on failure
-        */
-       abstract public function getFileStat( array $params );
-
-       /**
-        * Get a SHA-1 hash of the file at a storage path in the backend.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return string|bool Hash string or false on failure
-        */
-       abstract public function getFileSha1Base36( array $params );
-
-       /**
-        * Get the properties of the file at a storage path in the backend.
-        * Returns FSFile::placeholderProps() on failure.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return Array
-        */
-       abstract public function getFileProps( array $params );
-
-       /**
-        * Stream the file at a storage path in the backend.
-        * If the file does not exists, a 404 error will be given.
-        * Appropriate HTTP headers (Status, Content-Type, Content-Length)
-        * must be sent if streaming began, while none should be sent otherwise.
-        * Implementations should flush the output buffer before sending data.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src     : source storage path
-        *   - headers : additional HTTP headers to send on success
-        *   - latest  : use the latest available data
-        * @return Status
-        */
-       abstract public function streamFile( array $params );
-
-       /**
-        * Returns a file system file, identical to the file at a storage path.
-        * The file returned is either:
-        *   - a) A local copy of the file at a storage path in the backend.
-        *        The temporary copy will have the same extension as the source.
-        *   - b) An original of the file at a storage path in the backend.
-        * Temporary files may be purged when the file object falls out of scope.
-        *
-        * Write operations should *never* be done on this file as some backends
-        * may do internal tracking or may be instances of FileBackendMultiWrite.
-        * In that later case, there are copies of the file that must stay in sync.
-        * Additionally, further calls to this function may return the same file.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return FSFile|null Returns null on failure
-        */
-       abstract public function getLocalReference( array $params );
-
-       /**
-        * Get a local copy on disk of the file at a storage path in the backend.
-        * The temporary copy will have the same file extension as the source.
-        * Temporary files may be purged when the file object falls out of scope.
-        *
-        * @param $params Array
-        * $params include:
-        *   - src    : source storage path
-        *   - latest : use the latest available data
-        * @return TempFSFile|null Returns null on failure
-        */
-       abstract public function getLocalCopy( array $params );
-
-       /**
-        * Check if a directory exists at a given storage path.
-        * Backends using key/value stores will check if the path is a
-        * virtual directory, meaning there are files under the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * @param $params array
-        * $params include:
-        *   - dir : storage directory
-        * @return bool|null Returns null on failure
-        * @since 1.20
-        */
-       abstract public function directoryExists( array $params );
-
-       /**
-        * Get an iterator to list *all* directories under a storage directory.
-        * If the directory is of the form "mwstore://backend/container",
-        * then all directories in the container should be listed.
-        * If the directory is of form "mwstore://backend/container/dir",
-        * then all directories directly under that directory should be listed.
-        * Results should be storage directories relative to the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * @param $params array
-        * $params include:
-        *   - dir     : storage directory
-        *   - topOnly : only return direct child dirs of the directory
-        * @return Traversable|Array|null Returns null on failure
-        * @since 1.20
-        */
-       abstract public function getDirectoryList( array $params );
-
-       /**
-        * Same as FileBackend::getDirectoryList() except only lists
-        * directories that are immediately under the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * @param $params array
-        * $params include:
-        *   - dir : storage directory
-        * @return Traversable|Array|null Returns null on failure
-        * @since 1.20
-        */
-       final public function getTopDirectoryList( array $params ) {
-               return $this->getDirectoryList( array( 'topOnly' => true ) + $params );
-       }
-
-       /**
-        * Get an iterator to list *all* stored files under a storage directory.
-        * If the directory is of the form "mwstore://backend/container",
-        * then all files in the container should be listed.
-        * If the directory is of form "mwstore://backend/container/dir",
-        * then all files under that directory should be listed.
-        * Results should be storage paths relative to the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * @param $params array
-        * $params include:
-        *   - dir     : storage directory
-        *   - topOnly : only return direct child files of the directory (since 1.20)
-        * @return Traversable|Array|null Returns null on failure
-        */
-       abstract public function getFileList( array $params );
-
-       /**
-        * Same as FileBackend::getFileList() except only lists
-        * files that are immediately under the given directory.
-        *
-        * Storage backends with eventual consistency might return stale data.
-        *
-        * @param $params array
-        * $params include:
-        *   - dir : storage directory
-        * @return Traversable|Array|null Returns null on failure
-        * @since 1.20
-        */
-       final public function getTopFileList( array $params ) {
-               return $this->getFileList( array( 'topOnly' => true ) + $params );
-       }
-
-       /**
-        * Invalidate any in-process file existence and property cache.
-        * If $paths is given, then only the cache for those files will be cleared.
-        *
-        * @param $paths Array Storage paths (optional)
-        * @return void
-        */
-       public function clearCache( array $paths = null ) {}
-
-       /**
-        * Lock the files at the given storage paths in the backend.
-        * This will either lock all the files or none (on failure).
-        *
-        * Callers should consider using getScopedFileLocks() instead.
-        *
-        * @param $paths Array Storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @return Status
-        */
-       final public function lockFiles( array $paths, $type ) {
-               return $this->lockManager->lock( $paths, $type );
-       }
-
-       /**
-        * Unlock the files at the given storage paths in the backend.
-        *
-        * @param $paths Array Storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @return Status
-        */
-       final public function unlockFiles( array $paths, $type ) {
-               return $this->lockManager->unlock( $paths, $type );
-       }
-
-       /**
-        * Lock the files at the given storage paths in the backend.
-        * This will either lock all the files or none (on failure).
-        * On failure, the status object will be updated with errors.
-        *
-        * Once the return value goes out scope, the locks will be released and
-        * the status updated. Unlock fatals will not change the status "OK" value.
-        *
-        * @param $paths Array Storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @param $status Status Status to update on lock/unlock
-        * @return ScopedLock|null Returns null on failure
-        */
-       final public function getScopedFileLocks( array $paths, $type, Status $status ) {
-               return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
-       }
-
-       /**
-        * Get an array of scoped locks needed for a batch of file operations.
-        *
-        * Normally, FileBackend::doOperations() handles locking, unless
-        * the 'nonLocking' param is passed in. This function is useful if you
-        * want the files to be locked for a broader scope than just when the
-        * files are changing. For example, if you need to update DB metadata,
-        * you may want to keep the files locked until finished.
-        *
-        * @see FileBackend::doOperations()
-        *
-        * @param $ops Array List of file operations to FileBackend::doOperations()
-        * @param $status Status Status to update on lock/unlock
-        * @return Array List of ScopedFileLocks or null values
-        * @since 1.20
-        */
-       abstract public function getScopedLocksForOps( array $ops, Status $status );
-
-       /**
-        * Get the root storage path of this backend.
-        * All container paths are "subdirectories" of this path.
-        *
-        * @return string Storage path
-        * @since 1.20
-        */
-       final public function getRootStoragePath() {
-               return "mwstore://{$this->name}";
-       }
-
-       /**
-        * Get the file journal object for this backend
-        *
-        * @return FileJournal
-        */
-       final public function getJournal() {
-               return $this->fileJournal;
-       }
-
-       /**
-        * Check if a given path is a "mwstore://" path.
-        * This does not do any further validation or any existence checks.
-        *
-        * @param $path string
-        * @return bool
-        */
-       final public static function isStoragePath( $path ) {
-               return ( strpos( $path, 'mwstore://' ) === 0 );
-       }
-
-       /**
-        * Split a storage path into a backend name, a container name,
-        * and a relative file path. The relative path may be the empty string.
-        * This does not do any path normalization or traversal checks.
-        *
-        * @param $storagePath string
-        * @return Array (backend, container, rel object) or (null, null, null)
-        */
-       final public static function splitStoragePath( $storagePath ) {
-               if ( self::isStoragePath( $storagePath ) ) {
-                       // Remove the "mwstore://" prefix and split the path
-                       $parts = explode( '/', substr( $storagePath, 10 ), 3 );
-                       if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
-                               if ( count( $parts ) == 3 ) {
-                                       return $parts; // e.g. "backend/container/path"
-                               } else {
-                                       return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
-                               }
-                       }
-               }
-               return array( null, null, null );
-       }
-
-       /**
-        * Normalize a storage path by cleaning up directory separators.
-        * Returns null if the path is not of the format of a valid storage path.
-        *
-        * @param $storagePath string
-        * @return string|null
-        */
-       final public static function normalizeStoragePath( $storagePath ) {
-               list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
-               if ( $relPath !== null ) { // must be for this backend
-                       $relPath = self::normalizeContainerPath( $relPath );
-                       if ( $relPath !== null ) {
-                               return ( $relPath != '' )
-                                       ? "mwstore://{$backend}/{$container}/{$relPath}"
-                                       : "mwstore://{$backend}/{$container}";
-                       }
-               }
-               return null;
-       }
-
-       /**
-        * Get the parent storage directory of a storage path.
-        * This returns a path like "mwstore://backend/container",
-        * "mwstore://backend/container/...", or null if there is no parent.
-        *
-        * @param $storagePath string
-        * @return string|null
-        */
-       final public static function parentStoragePath( $storagePath ) {
-               $storagePath = dirname( $storagePath );
-               list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath );
-               return ( $rel === null ) ? null : $storagePath;
-       }
-
-       /**
-        * Get the final extension from a storage or FS path
-        *
-        * @param $path string
-        * @return string
-        */
-       final public static function extensionFromPath( $path ) {
-               $i = strrpos( $path, '.' );
-               return strtolower( $i ? substr( $path, $i + 1 ) : '' );
-       }
-
-       /**
-        * Check if a relative path has no directory traversals
-        *
-        * @param $path string
-        * @return bool
-        * @since 1.20
-        */
-       final public static function isPathTraversalFree( $path ) {
-               return ( self::normalizeContainerPath( $path ) !== null );
-       }
-
-       /**
-        * Validate and normalize a relative storage path.
-        * Null is returned if the path involves directory traversal.
-        * Traversal is insecure for FS backends and broken for others.
-        *
-        * This uses the same traversal protection as Title::secureAndSplit().
-        *
-        * @param $path string Storage path relative to a container
-        * @return string|null
-        */
-       final protected static function normalizeContainerPath( $path ) {
-               // Normalize directory separators
-               $path = strtr( $path, '\\', '/' );
-               // Collapse any consecutive directory separators
-               $path = preg_replace( '![/]{2,}!', '/', $path );
-               // Remove any leading directory separator
-               $path = ltrim( $path, '/' );
-               // Use the same traversal protection as Title::secureAndSplit()
-               if ( strpos( $path, '.' ) !== false ) {
-                       if (
-                               $path === '.' ||
-                               $path === '..' ||
-                               strpos( $path, './' ) === 0 ||
-                               strpos( $path, '../' ) === 0 ||
-                               strpos( $path, '/./' ) !== false ||
-                               strpos( $path, '/../' ) !== false
-                       ) {
-                               return null;
-                       }
-               }
-               return $path;
-       }
-}
diff --git a/includes/filerepo/backend/FileBackendGroup.php b/includes/filerepo/backend/FileBackendGroup.php
deleted file mode 100644 (file)
index 8bbc96d..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-<?php
-/**
- * File backend registration handling.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * Class to handle file backend registration
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-class FileBackendGroup {
-       /**
-        * @var FileBackendGroup
-        */
-       protected static $instance = null;
-
-       /** @var Array (name => ('class' => string, 'config' => array, 'instance' => object)) */
-       protected $backends = array();
-
-       protected function __construct() {}
-
-       /**
-        * @return FileBackendGroup
-        */
-       public static function singleton() {
-               if ( self::$instance == null ) {
-                       self::$instance = new self();
-                       self::$instance->initFromGlobals();
-               }
-               return self::$instance;
-       }
-
-       /**
-        * Destroy the singleton instance
-        *
-        * @return void
-        */
-       public static function destroySingleton() {
-               self::$instance = null;
-       }
-
-       /**
-        * Register file backends from the global variables
-        *
-        * @return void
-        */
-       protected function initFromGlobals() {
-               global $wgLocalFileRepo, $wgForeignFileRepos, $wgFileBackends;
-
-               // Register explicitly defined backends
-               $this->register( $wgFileBackends );
-
-               $autoBackends = array();
-               // Automatically create b/c backends for file repos...
-               $repos = array_merge( $wgForeignFileRepos, array( $wgLocalFileRepo ) );
-               foreach ( $repos as $info ) {
-                       $backendName = $info['backend'];
-                       if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
-                               continue; // already defined (or set to the object for some reason)
-                       }
-                       $repoName = $info['name'];
-                       // Local vars that used to be FSRepo members...
-                       $directory = $info['directory'];
-                       $deletedDir = isset( $info['deletedDir'] )
-                               ? $info['deletedDir']
-                               : false; // deletion disabled
-                       $thumbDir = isset( $info['thumbDir'] )
-                               ? $info['thumbDir']
-                               : "{$directory}/thumb";
-                       $fileMode = isset( $info['fileMode'] )
-                               ? $info['fileMode']
-                               : 0644;
-                       // Get the FS backend configuration
-                       $autoBackends[] = array(
-                               'name'           => $backendName,
-                               'class'          => 'FSFileBackend',
-                               'lockManager'    => 'fsLockManager',
-                               'containerPaths' => array(
-                                       "{$repoName}-public"  => "{$directory}",
-                                       "{$repoName}-thumb"   => $thumbDir,
-                                       "{$repoName}-deleted" => $deletedDir,
-                                       "{$repoName}-temp"    => "{$directory}/temp"
-                               ),
-                               'fileMode'       => $fileMode,
-                       );
-               }
-
-               // Register implicitly defined backends
-               $this->register( $autoBackends );
-       }
-
-       /**
-        * Register an array of file backend configurations
-        *
-        * @param $configs Array
-        * @return void
-        * @throws MWException
-        */
-       protected function register( array $configs ) {
-               foreach ( $configs as $config ) {
-                       if ( !isset( $config['name'] ) ) {
-                               throw new MWException( "Cannot register a backend with no name." );
-                       }
-                       $name = $config['name'];
-                       if ( !isset( $config['class'] ) ) {
-                               throw new MWException( "Cannot register backend `{$name}` with no class." );
-                       }
-                       $class = $config['class'];
-
-                       unset( $config['class'] ); // backend won't need this
-                       $this->backends[$name] = array(
-                               'class'    => $class,
-                               'config'   => $config,
-                               'instance' => null
-                       );
-               }
-       }
-
-       /**
-        * Get the backend object with a given name
-        *
-        * @param $name string
-        * @return FileBackend
-        * @throws MWException
-        */
-       public function get( $name ) {
-               if ( !isset( $this->backends[$name] ) ) {
-                       throw new MWException( "No backend defined with the name `$name`." );
-               }
-               // Lazy-load the actual backend instance
-               if ( !isset( $this->backends[$name]['instance'] ) ) {
-                       $class = $this->backends[$name]['class'];
-                       $config = $this->backends[$name]['config'];
-                       $this->backends[$name]['instance'] = new $class( $config );
-               }
-               return $this->backends[$name]['instance'];
-       }
-
-       /**
-        * Get the config array for a backend object with a given name
-        *
-        * @param $name string
-        * @return Array
-        * @throws MWException
-        */
-       public function config( $name ) {
-               if ( !isset( $this->backends[$name] ) ) {
-                       throw new MWException( "No backend defined with the name `$name`." );
-               }
-               $class = $this->backends[$name]['class'];
-               return array( 'class' => $class ) + $this->backends[$name]['config'];
-       }
-
-       /**
-        * Get an appropriate backend object from a storage path
-        *
-        * @param $storagePath string
-        * @return FileBackend|null Backend or null on failure
-        */
-       public function backendFromPath( $storagePath ) {
-               list( $backend, $c, $p ) = FileBackend::splitStoragePath( $storagePath );
-               if ( $backend !== null && isset( $this->backends[$backend] ) ) {
-                       return $this->get( $backend );
-               }
-               return null;
-       }
-}
diff --git a/includes/filerepo/backend/FileBackendMultiWrite.php b/includes/filerepo/backend/FileBackendMultiWrite.php
deleted file mode 100644 (file)
index e9136d5..0000000
+++ /dev/null
@@ -1,568 +0,0 @@
-<?php
-/**
- * Proxy backend that mirrors writes to several internal backends.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * @brief Proxy backend that mirrors writes to several internal backends.
- *
- * This class defines a multi-write backend. Multiple backends can be
- * registered to this proxy backend and it will act as a single backend.
- * Use this when all access to those backends is through this proxy backend.
- * At least one of the backends must be declared the "master" backend.
- *
- * Only use this class when transitioning from one storage system to another.
- *
- * Read operations are only done on the 'master' backend for consistency.
- * Write operations are performed on all backends, in the order defined.
- * If an operation fails on one backend it will be rolled back from the others.
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-class FileBackendMultiWrite extends FileBackend {
-       /** @var Array Prioritized list of FileBackendStore objects */
-       protected $backends = array(); // array of (backend index => backends)
-       protected $masterIndex = -1; // integer; index of master backend
-       protected $syncChecks = 0; // integer bitfield
-
-       /* Possible internal backend consistency checks */
-       const CHECK_SIZE = 1;
-       const CHECK_TIME = 2;
-       const CHECK_SHA1 = 4;
-
-       /**
-        * Construct a proxy backend that consists of several internal backends.
-        * Locking, journaling, and read-only checks are handled by the proxy backend.
-        *
-        * Additional $config params include:
-        *   - backends   : Array of backend config and multi-backend settings.
-        *                  Each value is the config used in the constructor of a
-        *                  FileBackendStore class, but with these additional settings:
-        *                    - class         : The name of the backend class
-        *                    - isMultiMaster : This must be set for one backend.
-        *                    - template:     : If given a backend name, this will use
-        *                                      the config of that backend as a template.
-        *                                      Values specified here take precedence.
-        *   - syncChecks : Integer bitfield of internal backend sync checks to perform.
-        *                  Possible bits include the FileBackendMultiWrite::CHECK_* constants.
-        *                  There are constants for SIZE, TIME, and SHA1.
-        *                  The checks are done before allowing any file operations.
-        * @param $config Array
-        * @throws MWException
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-               $namesUsed = array();
-               // Construct backends here rather than via registration
-               // to keep these backends hidden from outside the proxy.
-               foreach ( $config['backends'] as $index => $config ) {
-                       if ( isset( $config['template'] ) ) {
-                               // Config is just a modified version of a registered backend's.
-                               // This should only be used when that config is used only by this backend.
-                               $config = $config + FileBackendGroup::singleton()->config( $config['template'] );
-                       }
-                       $name = $config['name'];
-                       if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
-                               throw new MWException( "Two or more backends defined with the name $name." );
-                       }
-                       $namesUsed[$name] = 1;
-                       // Alter certain sub-backend settings for sanity
-                       unset( $config['readOnly'] ); // use proxy backend setting
-                       unset( $config['fileJournal'] ); // use proxy backend journal
-                       $config['wikiId'] = $this->wikiId; // use the proxy backend wiki ID
-                       $config['lockManager'] = 'nullLockManager'; // lock under proxy backend
-                       if ( !empty( $config['isMultiMaster'] ) ) {
-                               if ( $this->masterIndex >= 0 ) {
-                                       throw new MWException( 'More than one master backend defined.' );
-                               }
-                               $this->masterIndex = $index; // this is the "master"
-                               $config['fileJournal'] = $this->fileJournal; // log under proxy backend
-                       }
-                       // Create sub-backend object
-                       if ( !isset( $config['class'] ) ) {
-                               throw new MWException( 'No class given for a backend config.' );
-                       }
-                       $class = $config['class'];
-                       $this->backends[$index] = new $class( $config );
-               }
-               if ( $this->masterIndex < 0 ) { // need backends and must have a master
-                       throw new MWException( 'No master backend defined.' );
-               }
-               $this->syncChecks = isset( $config['syncChecks'] )
-                       ? $config['syncChecks']
-                       : self::CHECK_SIZE;
-       }
-
-       /**
-        * @see FileBackend::doOperationsInternal()
-        * @return Status
-        */
-       final protected function doOperationsInternal( array $ops, array $opts ) {
-               $status = Status::newGood();
-
-               $mbe = $this->backends[$this->masterIndex]; // convenience
-
-               // Get the paths to lock from the master backend
-               $realOps = $this->substOpBatchPaths( $ops, $mbe );
-               $paths = $mbe->getPathsToLockForOpsInternal( $mbe->getOperationsInternal( $realOps ) );
-               // Get the paths under the proxy backend's name
-               $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
-               $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
-               // Try to lock those files for the scope of this function...
-               if ( empty( $opts['nonLocking'] ) ) {
-                       // Try to lock those files for the scope of this function...
-                       $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
-                       $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
-                       if ( !$status->isOK() ) {
-                               return $status; // abort
-                       }
-               }
-               // Clear any cache entries (after locks acquired)
-               $this->clearCache();
-               // Do a consistency check to see if the backends agree
-               $status->merge( $this->consistencyCheck( $this->fileStoragePathsForOps( $ops ) ) );
-               if ( !$status->isOK() ) {
-                       return $status; // abort
-               }
-               // Actually attempt the operation batch on the master backend...
-               $masterStatus = $mbe->doOperations( $realOps, $opts );
-               $status->merge( $masterStatus );
-               // Propagate the operations to the clone backends...
-               foreach ( $this->backends as $index => $backend ) {
-                       if ( $index !== $this->masterIndex ) { // not done already
-                               $realOps = $this->substOpBatchPaths( $ops, $backend );
-                               $status->merge( $backend->doOperations( $realOps, $opts ) );
-                       }
-               }
-               // Make 'success', 'successCount', and 'failCount' fields reflect
-               // the overall operation, rather than all the batches for each backend.
-               // Do this by only using success values from the master backend's batch.
-               $status->success = $masterStatus->success;
-               $status->successCount = $masterStatus->successCount;
-               $status->failCount = $masterStatus->failCount;
-
-               return $status;
-       }
-
-       /**
-        * Check that a set of files are consistent across all internal backends
-        *
-        * @param $paths Array
-        * @return Status
-        */
-       public function consistencyCheck( array $paths ) {
-               $status = Status::newGood();
-               if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
-                       return $status; // skip checks
-               }
-
-               $mBackend = $this->backends[$this->masterIndex];
-               foreach ( array_unique( $paths ) as $path ) {
-                       $params = array( 'src' => $path, 'latest' => true );
-                       $mParams = $this->substOpPaths( $params, $mBackend );
-                       // Stat the file on the 'master' backend
-                       $mStat = $mBackend->getFileStat( $mParams );
-                       if ( $this->syncChecks & self::CHECK_SHA1 ) {
-                               $mSha1 = $mBackend->getFileSha1( $mParams );
-                       } else {
-                               $mSha1 = false;
-                       }
-                       $mUsable = $mBackend->isPathUsableInternal( $mParams['src'] );
-                       // Check of all clone backends agree with the master...
-                       foreach ( $this->backends as $index => $cBackend ) {
-                               if ( $index === $this->masterIndex ) {
-                                       continue; // master
-                               }
-                               $cParams = $this->substOpPaths( $params, $cBackend );
-                               $cStat = $cBackend->getFileStat( $cParams );
-                               if ( $mStat ) { // file is in master
-                                       if ( !$cStat ) { // file should exist
-                                               $status->fatal( 'backend-fail-synced', $path );
-                                               continue;
-                                       }
-                                       if ( $this->syncChecks & self::CHECK_SIZE ) {
-                                               if ( $cStat['size'] != $mStat['size'] ) { // wrong size
-                                                       $status->fatal( 'backend-fail-synced', $path );
-                                                       continue;
-                                               }
-                                       }
-                                       if ( $this->syncChecks & self::CHECK_TIME ) {
-                                               $mTs = wfTimestamp( TS_UNIX, $mStat['mtime'] );
-                                               $cTs = wfTimestamp( TS_UNIX, $cStat['mtime'] );
-                                               if ( abs( $mTs - $cTs ) > 30 ) { // outdated file somewhere
-                                                       $status->fatal( 'backend-fail-synced', $path );
-                                                       continue;
-                                               }
-                                       }
-                                       if ( $this->syncChecks & self::CHECK_SHA1 ) {
-                                               if ( $cBackend->getFileSha1( $cParams ) !== $mSha1 ) { // wrong SHA1
-                                                       $status->fatal( 'backend-fail-synced', $path );
-                                                       continue;
-                                               }
-                                       }
-                               } else { // file is not in master
-                                       if ( $cStat ) { // file should not exist
-                                               $status->fatal( 'backend-fail-synced', $path );
-                                       }
-                               }
-                               if ( $mUsable !== $cBackend->isPathUsableInternal( $cParams['src'] ) ) {
-                                       $status->fatal( 'backend-fail-synced', $path );
-                               }
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * Get a list of file storage paths to read or write for a list of operations
-        *
-        * @param $ops Array Same format as doOperations()
-        * @return Array List of storage paths to files (does not include directories)
-        */
-       protected function fileStoragePathsForOps( array $ops ) {
-               $paths = array();
-               foreach ( $ops as $op ) {
-                       if ( isset( $op['src'] ) ) {
-                               $paths[] = $op['src'];
-                       }
-                       if ( isset( $op['srcs'] ) ) {
-                               $paths = array_merge( $paths, $op['srcs'] );
-                       }
-                       if ( isset( $op['dst'] ) ) {
-                               $paths[] = $op['dst'];
-                       }
-               }
-               return array_unique( $paths );
-       }
-
-       /**
-        * Substitute the backend name in storage path parameters
-        * for a set of operations with that of a given internal backend.
-        *
-        * @param $ops Array List of file operation arrays
-        * @param $backend FileBackendStore
-        * @return Array
-        */
-       protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
-               $newOps = array(); // operations
-               foreach ( $ops as $op ) {
-                       $newOp = $op; // operation
-                       foreach ( array( 'src', 'srcs', 'dst', 'dir' ) as $par ) {
-                               if ( isset( $newOp[$par] ) ) { // string or array
-                                       $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
-                               }
-                       }
-                       $newOps[] = $newOp;
-               }
-               return $newOps;
-       }
-
-       /**
-        * Same as substOpBatchPaths() but for a single operation
-        *
-        * @param $ops array File operation array
-        * @param $backend FileBackendStore
-        * @return Array
-        */
-       protected function substOpPaths( array $ops, FileBackendStore $backend ) {
-               $newOps = $this->substOpBatchPaths( array( $ops ), $backend );
-               return $newOps[0];
-       }
-
-       /**
-        * Substitute the backend of storage paths with an internal backend's name
-        *
-        * @param $paths Array|string List of paths or single string path
-        * @param $backend FileBackendStore
-        * @return Array|string
-        */
-       protected function substPaths( $paths, FileBackendStore $backend ) {
-               return preg_replace(
-                       '!^mwstore://' . preg_quote( $this->name ) . '/!',
-                       StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
-                       $paths // string or array
-               );
-       }
-
-       /**
-        * Substitute the backend of internal storage paths with the proxy backend's name
-        *
-        * @param $paths Array|string List of paths or single string path
-        * @return Array|string
-        */
-       protected function unsubstPaths( $paths ) {
-               return preg_replace(
-                       '!^mwstore://([^/]+)!',
-                       StringUtils::escapeRegexReplacement( "mwstore://{$this->name}" ),
-                       $paths // string or array
-               );
-       }
-
-       /**
-        * @see FileBackend::doQuickOperationsInternal()
-        * @return Status
-        */
-       protected function doQuickOperationsInternal( array $ops ) {
-               $status = Status::newGood();
-               // Do the operations on the master backend; setting Status fields...
-               $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
-               $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
-               $status->merge( $masterStatus );
-               // Propagate the operations to the clone backends...
-               foreach ( $this->backends as $index => $backend ) {
-                       if ( $index !== $this->masterIndex ) { // not done already
-                               $realOps = $this->substOpBatchPaths( $ops, $backend );
-                               $status->merge( $backend->doQuickOperations( $realOps ) );
-                       }
-               }
-               // Make 'success', 'successCount', and 'failCount' fields reflect
-               // the overall operation, rather than all the batches for each backend.
-               // Do this by only using success values from the master backend's batch.
-               $status->success = $masterStatus->success;
-               $status->successCount = $masterStatus->successCount;
-               $status->failCount = $masterStatus->failCount;
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::doPrepare()
-        * @return Status
-        */
-       protected function doPrepare( array $params ) {
-               $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doPrepare( $realParams ) );
-               }
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::doSecure()
-        * @param $params array
-        * @return Status
-        */
-       protected function doSecure( array $params ) {
-               $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doSecure( $realParams ) );
-               }
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::doPublish()
-        * @param $params array
-        * @return Status
-        */
-       protected function doPublish( array $params ) {
-               $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doPublish( $realParams ) );
-               }
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::doClean()
-        * @param $params array
-        * @return Status
-        */
-       protected function doClean( array $params ) {
-               $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doClean( $realParams ) );
-               }
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::concatenate()
-        * @param $params array
-        * @return Status
-        */
-       public function concatenate( array $params ) {
-               // We are writing to an FS file, so we don't need to do this per-backend
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->concatenate( $realParams );
-       }
-
-       /**
-        * @see FileBackend::fileExists()
-        * @param $params array
-        */
-       public function fileExists( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->fileExists( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getFileTimestamp()
-        * @param $params array
-        * @return bool|string
-        */
-       public function getFileTimestamp( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getFileSize()
-        * @param $params array
-        * @return bool|int
-        */
-       public function getFileSize( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getFileSize( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getFileStat()
-        * @param $params array
-        * @return Array|bool|null
-        */
-       public function getFileStat( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getFileStat( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getFileContents()
-        * @param $params array
-        * @return bool|string
-        */
-       public function getFileContents( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getFileContents( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getFileSha1Base36()
-        * @param $params array
-        * @return bool|string
-        */
-       public function getFileSha1Base36( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getFileProps()
-        * @param $params array
-        * @return Array
-        */
-       public function getFileProps( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getFileProps( $realParams );
-       }
-
-       /**
-        * @see FileBackend::streamFile()
-        * @param $params array
-        * @return \Status
-        */
-       public function streamFile( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->streamFile( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getLocalReference()
-        * @param $params array
-        * @return FSFile|null
-        */
-       public function getLocalReference( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getLocalReference( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getLocalCopy()
-        * @param $params array
-        * @return null|TempFSFile
-        */
-       public function getLocalCopy( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getLocalCopy( $realParams );
-       }
-
-       /**
-        * @see FileBackend::directoryExists()
-        * @param $params array
-        * @return bool|null
-        */
-       public function directoryExists( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->directoryExists( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getSubdirectoryList()
-        * @param $params array
-        * @return Array|null|Traversable
-        */
-       public function getDirectoryList( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
-       }
-
-       /**
-        * @see FileBackend::getFileList()
-        * @param $params array
-        * @return Array|null|\Traversable
-        */
-       public function getFileList( array $params ) {
-               $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
-               return $this->backends[$this->masterIndex]->getFileList( $realParams );
-       }
-
-       /**
-        * @see FileBackend::clearCache()
-        */
-       public function clearCache( array $paths = null ) {
-               foreach ( $this->backends as $backend ) {
-                       $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
-                       $backend->clearCache( $realPaths );
-               }
-       }
-
-       /**
-        * @see FileBackend::getScopedLocksForOps()
-        */
-       public function getScopedLocksForOps( array $ops, Status $status ) {
-               $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $ops );
-               // Get the paths to lock from the master backend
-               $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
-               // Get the paths under the proxy backend's name
-               $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
-               $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
-               return array(
-                       $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
-                       $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
-               );
-       }
-}
diff --git a/includes/filerepo/backend/FileBackendStore.php b/includes/filerepo/backend/FileBackendStore.php
deleted file mode 100644 (file)
index 852a653..0000000
+++ /dev/null
@@ -1,1730 +0,0 @@
-<?php
-/**
- * Base class for all backends using particular storage medium.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * @brief Base class for all backends using particular storage medium.
- *
- * This class defines the methods as abstract that subclasses must implement.
- * Outside callers should *not* use functions with "Internal" in the name.
- *
- * The FileBackend operations are implemented using basic functions
- * such as storeInternal(), copyInternal(), deleteInternal() and the like.
- * This class is also responsible for path resolution and sanitization.
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-abstract class FileBackendStore extends FileBackend {
-       /** @var BagOStuff */
-       protected $memCache;
-       /** @var ProcessCacheLRU */
-       protected $cheapCache; // Map of paths to small (RAM/disk) cache items
-       /** @var ProcessCacheLRU */
-       protected $expensiveCache; // Map of paths to large (RAM/disk) cache items
-
-       /** @var Array Map of container names to sharding settings */
-       protected $shardViaHashLevels = array(); // (container name => config array)
-
-       protected $maxFileSize = 4294967296; // integer bytes (4GiB)
-
-       /**
-        * @see FileBackend::__construct()
-        *
-        * @param $config Array
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-               $this->memCache       = new EmptyBagOStuff(); // disabled by default
-               $this->cheapCache     = new ProcessCacheLRU( 300 );
-               $this->expensiveCache = new ProcessCacheLRU( 5 );
-       }
-
-       /**
-        * Get the maximum allowable file size given backend
-        * medium restrictions and basic performance constraints.
-        * Do not call this function from places outside FileBackend and FileOp.
-        *
-        * @return integer Bytes
-        */
-       final public function maxFileSizeInternal() {
-               return $this->maxFileSize;
-       }
-
-       /**
-        * Check if a file can be created at a given storage path.
-        * FS backends should check if the parent directory exists and the file is writable.
-        * Backends using key/value stores should check if the container exists.
-        *
-        * @param $storagePath string
-        * @return bool
-        */
-       abstract public function isPathUsableInternal( $storagePath );
-
-       /**
-        * Create a file in the backend with the given contents.
-        * Do not call this function from places outside FileBackend and FileOp.
-        *
-        * $params include:
-        *   - content       : the raw file contents
-        *   - dst           : destination storage path
-        *   - overwrite     : overwrite any file that exists at the destination
-        *   - async         : Status will be returned immediately if supported.
-        *                     If the status is OK, then its value field will be
-        *                     set to a FileBackendStoreOpHandle object.
-        *
-        * @param $params Array
-        * @return Status
-        */
-       final public function createInternal( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
-                       $status = Status::newFatal( 'backend-fail-maxsize',
-                               $params['dst'], $this->maxFileSizeInternal() );
-               } else {
-                       $status = $this->doCreateInternal( $params );
-                       $this->clearCache( array( $params['dst'] ) );
-                       $this->deleteFileCache( $params['dst'] ); // persistent cache
-               }
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::createInternal()
-        */
-       abstract protected function doCreateInternal( array $params );
-
-       /**
-        * Store a file into the backend from a file on disk.
-        * Do not call this function from places outside FileBackend and FileOp.
-        *
-        * $params include:
-        *   - src           : source path on disk
-        *   - dst           : destination storage path
-        *   - overwrite     : overwrite any file that exists at the destination
-        *   - async         : Status will be returned immediately if supported.
-        *                     If the status is OK, then its value field will be
-        *                     set to a FileBackendStoreOpHandle object.
-        *
-        * @param $params Array
-        * @return Status
-        */
-       final public function storeInternal( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
-                       $status = Status::newFatal( 'backend-fail-maxsize',
-                               $params['dst'], $this->maxFileSizeInternal() );
-               } else {
-                       $status = $this->doStoreInternal( $params );
-                       $this->clearCache( array( $params['dst'] ) );
-                       $this->deleteFileCache( $params['dst'] ); // persistent cache
-               }
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::storeInternal()
-        */
-       abstract protected function doStoreInternal( array $params );
-
-       /**
-        * Copy a file from one storage path to another in the backend.
-        * Do not call this function from places outside FileBackend and FileOp.
-        *
-        * $params include:
-        *   - src           : source storage path
-        *   - dst           : destination storage path
-        *   - overwrite     : overwrite any file that exists at the destination
-        *   - async         : Status will be returned immediately if supported.
-        *                     If the status is OK, then its value field will be
-        *                     set to a FileBackendStoreOpHandle object.
-        *
-        * @param $params Array
-        * @return Status
-        */
-       final public function copyInternal( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = $this->doCopyInternal( $params );
-               $this->clearCache( array( $params['dst'] ) );
-               $this->deleteFileCache( $params['dst'] ); // persistent cache
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::copyInternal()
-        */
-       abstract protected function doCopyInternal( array $params );
-
-       /**
-        * Delete a file at the storage path.
-        * Do not call this function from places outside FileBackend and FileOp.
-        *
-        * $params include:
-        *   - src                 : source storage path
-        *   - ignoreMissingSource : do nothing if the source file does not exist
-        *   - async               : Status will be returned immediately if supported.
-        *                           If the status is OK, then its value field will be
-        *                           set to a FileBackendStoreOpHandle object.
-        *
-        * @param $params Array
-        * @return Status
-        */
-       final public function deleteInternal( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = $this->doDeleteInternal( $params );
-               $this->clearCache( array( $params['src'] ) );
-               $this->deleteFileCache( $params['src'] ); // persistent cache
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::deleteInternal()
-        */
-       abstract protected function doDeleteInternal( array $params );
-
-       /**
-        * Move a file from one storage path to another in the backend.
-        * Do not call this function from places outside FileBackend and FileOp.
-        *
-        * $params include:
-        *   - src           : source storage path
-        *   - dst           : destination storage path
-        *   - overwrite     : overwrite any file that exists at the destination
-        *   - async         : Status will be returned immediately if supported.
-        *                     If the status is OK, then its value field will be
-        *                     set to a FileBackendStoreOpHandle object.
-        *
-        * @param $params Array
-        * @return Status
-        */
-       final public function moveInternal( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = $this->doMoveInternal( $params );
-               $this->clearCache( array( $params['src'], $params['dst'] ) );
-               $this->deleteFileCache( $params['src'] ); // persistent cache
-               $this->deleteFileCache( $params['dst'] ); // persistent cache
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::moveInternal()
-        * @return Status
-        */
-       protected function doMoveInternal( array $params ) {
-               unset( $params['async'] ); // two steps, won't work here :)
-               // Copy source to dest
-               $status = $this->copyInternal( $params );
-               if ( $status->isOK() ) {
-                       // Delete source (only fails due to races or medium going down)
-                       $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
-                       $status->setResult( true, $status->value ); // ignore delete() errors
-               }
-               return $status;
-       }
-
-       /**
-        * No-op file operation that does nothing.
-        * Do not call this function from places outside FileBackend and FileOp.
-        *
-        * @param $params Array
-        * @return Status
-        */
-       final public function nullInternal( array $params ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see FileBackend::concatenate()
-        * @return Status
-        */
-       final public function concatenate( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = Status::newGood();
-
-               // Try to lock the source files for the scope of this function
-               $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
-               if ( $status->isOK() ) {
-                       // Actually do the concatenation
-                       $status->merge( $this->doConcatenate( $params ) );
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::concatenate()
-        * @return Status
-        */
-       protected function doConcatenate( array $params ) {
-               $status = Status::newGood();
-               $tmpPath = $params['dst']; // convenience
-
-               // Check that the specified temp file is valid...
-               wfSuppressWarnings();
-               $ok = ( is_file( $tmpPath ) && !filesize( $tmpPath ) );
-               wfRestoreWarnings();
-               if ( !$ok ) { // not present or not empty
-                       $status->fatal( 'backend-fail-opentemp', $tmpPath );
-                       return $status;
-               }
-
-               // Build up the temp file using the source chunks (in order)...
-               $tmpHandle = fopen( $tmpPath, 'ab' );
-               if ( $tmpHandle === false ) {
-                       $status->fatal( 'backend-fail-opentemp', $tmpPath );
-                       return $status;
-               }
-               foreach ( $params['srcs'] as $virtualSource ) {
-                       // Get a local FS version of the chunk
-                       $tmpFile = $this->getLocalReference( array( 'src' => $virtualSource ) );
-                       if ( !$tmpFile ) {
-                               $status->fatal( 'backend-fail-read', $virtualSource );
-                               return $status;
-                       }
-                       // Get a handle to the local FS version
-                       $sourceHandle = fopen( $tmpFile->getPath(), 'r' );
-                       if ( $sourceHandle === false ) {
-                               fclose( $tmpHandle );
-                               $status->fatal( 'backend-fail-read', $virtualSource );
-                               return $status;
-                       }
-                       // Append chunk to file (pass chunk size to avoid magic quotes)
-                       if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) {
-                               fclose( $sourceHandle );
-                               fclose( $tmpHandle );
-                               $status->fatal( 'backend-fail-writetemp', $tmpPath );
-                               return $status;
-                       }
-                       fclose( $sourceHandle );
-               }
-               if ( !fclose( $tmpHandle ) ) {
-                       $status->fatal( 'backend-fail-closetemp', $tmpPath );
-                       return $status;
-               }
-
-               clearstatcache(); // temp file changed
-
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::doPrepare()
-        * @return Status
-        */
-       final protected function doPrepare( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-
-               $status = Status::newGood();
-               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
-               if ( $dir === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
-                       wfProfileOut( __METHOD__ . '-' . $this->name );
-                       wfProfileOut( __METHOD__ );
-                       return $status; // invalid storage path
-               }
-
-               if ( $shard !== null ) { // confined to a single container/shard
-                       $status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) );
-               } else { // directory is on several shards
-                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
-                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
-                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
-                               $status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) );
-                       }
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doPrepare()
-        * @return Status
-        */
-       protected function doPrepareInternal( $container, $dir, array $params ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see FileBackend::doSecure()
-        * @return Status
-        */
-       final protected function doSecure( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = Status::newGood();
-
-               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
-               if ( $dir === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
-                       wfProfileOut( __METHOD__ . '-' . $this->name );
-                       wfProfileOut( __METHOD__ );
-                       return $status; // invalid storage path
-               }
-
-               if ( $shard !== null ) { // confined to a single container/shard
-                       $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
-               } else { // directory is on several shards
-                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
-                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
-                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
-                               $status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) );
-                       }
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doSecure()
-        * @return Status
-        */
-       protected function doSecureInternal( $container, $dir, array $params ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see FileBackend::doPublish()
-        * @return Status
-        */
-       final protected function doPublish( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = Status::newGood();
-
-               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
-               if ( $dir === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
-                       wfProfileOut( __METHOD__ . '-' . $this->name );
-                       wfProfileOut( __METHOD__ );
-                       return $status; // invalid storage path
-               }
-
-               if ( $shard !== null ) { // confined to a single container/shard
-                       $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
-               } else { // directory is on several shards
-                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
-                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
-                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
-                               $status->merge( $this->doPublishInternal( "{$fullCont}{$suffix}", $dir, $params ) );
-                       }
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doPublish()
-        * @return Status
-        */
-       protected function doPublishInternal( $container, $dir, array $params ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see FileBackend::doClean()
-        * @return Status
-        */
-       final protected function doClean( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = Status::newGood();
-
-               // Recursive: first delete all empty subdirs recursively
-               if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) {
-                       $subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) );
-                       if ( $subDirsRel !== null ) { // no errors
-                               foreach ( $subDirsRel as $subDirRel ) {
-                                       $subDir = $params['dir'] . "/{$subDirRel}"; // full path
-                                       $status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) );
-                               }
-                       }
-               }
-
-               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
-               if ( $dir === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
-                       wfProfileOut( __METHOD__ . '-' . $this->name );
-                       wfProfileOut( __METHOD__ );
-                       return $status; // invalid storage path
-               }
-
-               // Attempt to lock this directory...
-               $filesLockEx = array( $params['dir'] );
-               $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
-               if ( !$status->isOK() ) {
-                       wfProfileOut( __METHOD__ . '-' . $this->name );
-                       wfProfileOut( __METHOD__ );
-                       return $status; // abort
-               }
-
-               if ( $shard !== null ) { // confined to a single container/shard
-                       $status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) );
-                       $this->deleteContainerCache( $fullCont ); // purge cache
-               } else { // directory is on several shards
-                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
-                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
-                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
-                               $status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) );
-                               $this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache
-                       }
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doClean()
-        * @return Status
-        */
-       protected function doCleanInternal( $container, $dir, array $params ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see FileBackend::fileExists()
-        * @return bool|null
-        */
-       final public function fileExists( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $stat = $this->getFileStat( $params );
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return ( $stat === null ) ? null : (bool)$stat; // null => failure
-       }
-
-       /**
-        * @see FileBackend::getFileTimestamp()
-        * @return bool
-        */
-       final public function getFileTimestamp( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $stat = $this->getFileStat( $params );
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $stat ? $stat['mtime'] : false;
-       }
-
-       /**
-        * @see FileBackend::getFileSize()
-        * @return bool
-        */
-       final public function getFileSize( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $stat = $this->getFileStat( $params );
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $stat ? $stat['size'] : false;
-       }
-
-       /**
-        * @see FileBackend::getFileStat()
-        * @return bool
-        */
-       final public function getFileStat( array $params ) {
-               $path = self::normalizeStoragePath( $params['src'] );
-               if ( $path === null ) {
-                       return false; // invalid storage path
-               }
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $latest = !empty( $params['latest'] ); // use latest data?
-               if ( !$this->cheapCache->has( $path, 'stat' ) ) {
-                       $this->primeFileCache( array( $path ) ); // check persistent cache
-               }
-               if ( $this->cheapCache->has( $path, 'stat' ) ) {
-                       $stat = $this->cheapCache->get( $path, 'stat' );
-                       // If we want the latest data, check that this cached
-                       // value was in fact fetched with the latest available data.
-                       if ( !$latest || $stat['latest'] ) {
-                               wfProfileOut( __METHOD__ . '-' . $this->name );
-                               wfProfileOut( __METHOD__ );
-                               return $stat;
-                       }
-               }
-               wfProfileIn( __METHOD__ . '-miss' );
-               wfProfileIn( __METHOD__ . '-miss-' . $this->name );
-               $stat = $this->doGetFileStat( $params );
-               wfProfileOut( __METHOD__ . '-miss-' . $this->name );
-               wfProfileOut( __METHOD__ . '-miss' );
-               if ( is_array( $stat ) ) { // don't cache negatives
-                       $stat['latest'] = $latest;
-                       $this->cheapCache->set( $path, 'stat', $stat );
-                       $this->setFileCache( $path, $stat ); // update persistent cache
-                       if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
-                               $this->cheapCache->set( $path, 'sha1',
-                                       array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
-                       }
-               } else {
-                       wfDebug( __METHOD__ . ": File $path does not exist.\n" );
-               }
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $stat;
-       }
-
-       /**
-        * @see FileBackendStore::getFileStat()
-        */
-       abstract protected function doGetFileStat( array $params );
-
-       /**
-        * @see FileBackend::getFileContents()
-        * @return bool|string
-        */
-       public function getFileContents( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $tmpFile = $this->getLocalReference( $params );
-               if ( !$tmpFile ) {
-                       wfProfileOut( __METHOD__ . '-' . $this->name );
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-               wfSuppressWarnings();
-               $data = file_get_contents( $tmpFile->getPath() );
-               wfRestoreWarnings();
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $data;
-       }
-
-       /**
-        * @see FileBackend::getFileSha1Base36()
-        * @return bool|string
-        */
-       final public function getFileSha1Base36( array $params ) {
-               $path = self::normalizeStoragePath( $params['src'] );
-               if ( $path === null ) {
-                       return false; // invalid storage path
-               }
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $latest = !empty( $params['latest'] ); // use latest data?
-               if ( $this->cheapCache->has( $path, 'sha1' ) ) {
-                       $stat = $this->cheapCache->get( $path, 'sha1' );
-                       // If we want the latest data, check that this cached
-                       // value was in fact fetched with the latest available data.
-                       if ( !$latest || $stat['latest'] ) {
-                               wfProfileOut( __METHOD__ . '-' . $this->name );
-                               wfProfileOut( __METHOD__ );
-                               return $stat['hash'];
-                       }
-               }
-               wfProfileIn( __METHOD__ . '-miss' );
-               wfProfileIn( __METHOD__ . '-miss-' . $this->name );
-               $hash = $this->doGetFileSha1Base36( $params );
-               wfProfileOut( __METHOD__ . '-miss-' . $this->name );
-               wfProfileOut( __METHOD__ . '-miss' );
-               if ( $hash ) { // don't cache negatives
-                       $this->cheapCache->set( $path, 'sha1',
-                               array( 'hash' => $hash, 'latest' => $latest ) );
-               }
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $hash;
-       }
-
-       /**
-        * @see FileBackendStore::getFileSha1Base36()
-        * @return bool|string
-        */
-       protected function doGetFileSha1Base36( array $params ) {
-               $fsFile = $this->getLocalReference( $params );
-               if ( !$fsFile ) {
-                       return false;
-               } else {
-                       return $fsFile->getSha1Base36();
-               }
-       }
-
-       /**
-        * @see FileBackend::getFileProps()
-        * @return Array
-        */
-       final public function getFileProps( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $fsFile = $this->getLocalReference( $params );
-               $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $props;
-       }
-
-       /**
-        * @see FileBackend::getLocalReference()
-        * @return TempFSFile|null
-        */
-       public function getLocalReference( array $params ) {
-               $path = self::normalizeStoragePath( $params['src'] );
-               if ( $path === null ) {
-                       return null; // invalid storage path
-               }
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $latest = !empty( $params['latest'] ); // use latest data?
-               if ( $this->expensiveCache->has( $path, 'localRef' ) ) {
-                       $val = $this->expensiveCache->get( $path, 'localRef' );
-                       // If we want the latest data, check that this cached
-                       // value was in fact fetched with the latest available data.
-                       if ( !$latest || $val['latest'] ) {
-                               wfProfileOut( __METHOD__ . '-' . $this->name );
-                               wfProfileOut( __METHOD__ );
-                               return $val['object'];
-                       }
-               }
-               $tmpFile = $this->getLocalCopy( $params );
-               if ( $tmpFile ) { // don't cache negatives
-                       $this->expensiveCache->set( $path, 'localRef',
-                               array( 'object' => $tmpFile, 'latest' => $latest ) );
-               }
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $tmpFile;
-       }
-
-       /**
-        * @see FileBackend::streamFile()
-        * @return Status
-        */
-       final public function streamFile( array $params ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = Status::newGood();
-
-               $info = $this->getFileStat( $params );
-               if ( !$info ) { // let StreamFile handle the 404
-                       $status->fatal( 'backend-fail-notexists', $params['src'] );
-               }
-
-               // Set output buffer and HTTP headers for stream
-               $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array();
-               $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders );
-               if ( $res == StreamFile::NOT_MODIFIED ) {
-                       // do nothing; client cache is up to date
-               } elseif ( $res == StreamFile::READY_STREAM ) {
-                       wfProfileIn( __METHOD__ . '-send' );
-                       wfProfileIn( __METHOD__ . '-send-' . $this->name );
-                       $status = $this->doStreamFile( $params );
-                       wfProfileOut( __METHOD__ . '-send-' . $this->name );
-                       wfProfileOut( __METHOD__ . '-send' );
-               } else {
-                       $status->fatal( 'backend-fail-stream', $params['src'] );
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::streamFile()
-        * @return Status
-        */
-       protected function doStreamFile( array $params ) {
-               $status = Status::newGood();
-
-               $fsFile = $this->getLocalReference( $params );
-               if ( !$fsFile ) {
-                       $status->fatal( 'backend-fail-stream', $params['src'] );
-               } elseif ( !readfile( $fsFile->getPath() ) ) {
-                       $status->fatal( 'backend-fail-stream', $params['src'] );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::directoryExists()
-        * @return bool|null
-        */
-       final public function directoryExists( array $params ) {
-               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
-               if ( $dir === null ) {
-                       return false; // invalid storage path
-               }
-               if ( $shard !== null ) { // confined to a single container/shard
-                       return $this->doDirectoryExists( $fullCont, $dir, $params );
-               } else { // directory is on several shards
-                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
-                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
-                       $res = false; // response
-                       foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
-                               $exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
-                               if ( $exists ) {
-                                       $res = true;
-                                       break; // found one!
-                               } elseif ( $exists === null ) { // error?
-                                       $res = null; // if we don't find anything, it is indeterminate
-                               }
-                       }
-                       return $res;
-               }
-       }
-
-       /**
-        * @see FileBackendStore::directoryExists()
-        *
-        * @param $container string Resolved container name
-        * @param $dir string Resolved path relative to container
-        * @param $params Array
-        * @return bool|null
-        */
-       abstract protected function doDirectoryExists( $container, $dir, array $params );
-
-       /**
-        * @see FileBackend::getDirectoryList()
-        * @return Traversable|Array|null Returns null on failure
-        */
-       final public function getDirectoryList( array $params ) {
-               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
-               if ( $dir === null ) { // invalid storage path
-                       return null;
-               }
-               if ( $shard !== null ) {
-                       // File listing is confined to a single container/shard
-                       return $this->getDirectoryListInternal( $fullCont, $dir, $params );
-               } else {
-                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
-                       // File listing spans multiple containers/shards
-                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
-                       return new FileBackendStoreShardDirIterator( $this,
-                               $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
-               }
-       }
-
-       /**
-        * Do not call this function from places outside FileBackend
-        *
-        * @see FileBackendStore::getDirectoryList()
-        *
-        * @param $container string Resolved container name
-        * @param $dir string Resolved path relative to container
-        * @param $params Array
-        * @return Traversable|Array|null Returns null on failure
-        */
-       abstract public function getDirectoryListInternal( $container, $dir, array $params );
-
-       /**
-        * @see FileBackend::getFileList()
-        * @return Traversable|Array|null Returns null on failure
-        */
-       final public function getFileList( array $params ) {
-               list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
-               if ( $dir === null ) { // invalid storage path
-                       return null;
-               }
-               if ( $shard !== null ) {
-                       // File listing is confined to a single container/shard
-                       return $this->getFileListInternal( $fullCont, $dir, $params );
-               } else {
-                       wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
-                       // File listing spans multiple containers/shards
-                       list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
-                       return new FileBackendStoreShardFileIterator( $this,
-                               $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
-               }
-       }
-
-       /**
-        * Do not call this function from places outside FileBackend
-        *
-        * @see FileBackendStore::getFileList()
-        *
-        * @param $container string Resolved container name
-        * @param $dir string Resolved path relative to container
-        * @param $params Array
-        * @return Traversable|Array|null Returns null on failure
-        */
-       abstract public function getFileListInternal( $container, $dir, array $params );
-
-       /**
-        * Return a list of FileOp objects from a list of operations.
-        * Do not call this function from places outside FileBackend.
-        *
-        * The result must have the same number of items as the input.
-        * An exception is thrown if an unsupported operation is requested.
-        *
-        * @param $ops Array Same format as doOperations()
-        * @return Array List of FileOp objects
-        * @throws MWException
-        */
-       final public function getOperationsInternal( array $ops ) {
-               $supportedOps = array(
-                       'store'       => 'StoreFileOp',
-                       'copy'        => 'CopyFileOp',
-                       'move'        => 'MoveFileOp',
-                       'delete'      => 'DeleteFileOp',
-                       'create'      => 'CreateFileOp',
-                       'null'        => 'NullFileOp'
-               );
-
-               $performOps = array(); // array of FileOp objects
-               // Build up ordered array of FileOps...
-               foreach ( $ops as $operation ) {
-                       $opName = $operation['op'];
-                       if ( isset( $supportedOps[$opName] ) ) {
-                               $class = $supportedOps[$opName];
-                               // Get params for this operation
-                               $params = $operation;
-                               // Append the FileOp class
-                               $performOps[] = new $class( $this, $params );
-                       } else {
-                               throw new MWException( "Operation '$opName' is not supported." );
-                       }
-               }
-
-               return $performOps;
-       }
-
-       /**
-        * Get a list of storage paths to lock for a list of operations
-        * Returns an array with 'sh' (shared) and 'ex' (exclusive) keys,
-        * each corresponding to a list of storage paths to be locked.
-        *
-        * @param $performOps Array List of FileOp objects
-        * @return Array ('sh' => list of paths, 'ex' => list of paths)
-        */
-       final public function getPathsToLockForOpsInternal( array $performOps ) {
-               // Build up a list of files to lock...
-               $paths = array( 'sh' => array(), 'ex' => array() );
-               foreach ( $performOps as $fileOp ) {
-                       $paths['sh'] = array_merge( $paths['sh'], $fileOp->storagePathsRead() );
-                       $paths['ex'] = array_merge( $paths['ex'], $fileOp->storagePathsChanged() );
-               }
-               // Optimization: if doing an EX lock anyway, don't also set an SH one
-               $paths['sh'] = array_diff( $paths['sh'], $paths['ex'] );
-               // Get a shared lock on the parent directory of each path changed
-               $paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) );
-
-               return $paths;
-       }
-
-       /**
-        * @see FileBackend::getScopedLocksForOps()
-        * @return Array
-        */
-       public function getScopedLocksForOps( array $ops, Status $status ) {
-               $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
-               return array(
-                       $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
-                       $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
-               );
-       }
-
-       /**
-        * @see FileBackend::doOperationsInternal()
-        * @return Status
-        */
-       final protected function doOperationsInternal( array $ops, array $opts ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = Status::newGood();
-
-               // Build up a list of FileOps...
-               $performOps = $this->getOperationsInternal( $ops );
-
-               // Acquire any locks as needed...
-               if ( empty( $opts['nonLocking'] ) ) {
-                       // Build up a list of files to lock...
-                       $paths = $this->getPathsToLockForOpsInternal( $performOps );
-                       // Try to lock those files for the scope of this function...
-                       $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
-                       $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
-                       if ( !$status->isOK() ) {
-                               wfProfileOut( __METHOD__ . '-' . $this->name );
-                               wfProfileOut( __METHOD__ );
-                               return $status; // abort
-                       }
-               }
-
-               // Clear any file cache entries (after locks acquired)
-               $this->clearCache();
-
-               // Load from the persistent file and container caches
-               $this->primeFileCache( $performOps );
-               $this->primeContainerCache( $performOps );
-
-               // Actually attempt the operation batch...
-               $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
-
-               // Merge errors into status fields
-               $status->merge( $subStatus );
-               $status->success = $subStatus->success; // not done in merge()
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * @see FileBackend::doQuickOperationsInternal()
-        * @return Status
-        * @throws MWException
-        */
-       final protected function doQuickOperationsInternal( array $ops ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               $status = Status::newGood();
-
-               $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
-               $async = ( $this->parallelize === 'implicit' );
-               $maxConcurrency = $this->concurrency; // throttle
-
-               $statuses = array(); // array of (index => Status)
-               $fileOpHandles = array(); // list of (index => handle) arrays
-               $curFileOpHandles = array(); // current handle batch
-               // Perform the sync-only ops and build up op handles for the async ops...
-               foreach ( $ops as $index => $params ) {
-                       if ( !in_array( $params['op'], $supportedOps ) ) {
-                               wfProfileOut( __METHOD__ . '-' . $this->name );
-                               wfProfileOut( __METHOD__ );
-                               throw new MWException( "Operation '{$params['op']}' is not supported." );
-                       }
-                       $method = $params['op'] . 'Internal'; // e.g. "storeInternal"
-                       $subStatus = $this->$method( array( 'async' => $async ) + $params );
-                       if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async
-                               if ( count( $curFileOpHandles ) >= $maxConcurrency ) {
-                                       $fileOpHandles[] = $curFileOpHandles; // push this batch
-                                       $curFileOpHandles = array();
-                               }
-                               $curFileOpHandles[$index] = $subStatus->value; // keep index
-                       } else { // error or completed
-                               $statuses[$index] = $subStatus; // keep index
-                       }
-               }
-               if ( count( $curFileOpHandles ) ) {
-                       $fileOpHandles[] = $curFileOpHandles; // last batch
-               }
-               // Do all the async ops that can be done concurrently...
-               foreach ( $fileOpHandles as $fileHandleBatch ) {
-                       $statuses = $statuses + $this->executeOpHandlesInternal( $fileHandleBatch );
-               }
-               // Marshall and merge all the responses...
-               foreach ( $statuses as $index => $subStatus ) {
-                       $status->merge( $subStatus );
-                       if ( $subStatus->isOK() ) {
-                               $status->success[$index] = true;
-                               ++$status->successCount;
-                       } else {
-                               $status->success[$index] = false;
-                               ++$status->failCount;
-                       }
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * Execute a list of FileBackendStoreOpHandle handles in parallel.
-        * The resulting Status object fields will correspond
-        * to the order in which the handles where given.
-        *
-        * @param $handles Array List of FileBackendStoreOpHandle objects
-        * @return Array Map of Status objects
-        * @throws MWException
-        */
-       final public function executeOpHandlesInternal( array $fileOpHandles ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-               foreach ( $fileOpHandles as $fileOpHandle ) {
-                       if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
-                               throw new MWException( "Given a non-FileBackendStoreOpHandle object." );
-                       } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
-                               throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." );
-                       }
-               }
-               $res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
-               foreach ( $fileOpHandles as $fileOpHandle ) {
-                       $fileOpHandle->closeResources();
-               }
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-               return $res;
-       }
-
-       /**
-        * @see FileBackendStore::executeOpHandlesInternal()
-        * @return Array List of corresponding Status objects
-        */
-       protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
-               foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty
-                       throw new MWException( "This backend supports no asynchronous operations." );
-               }
-               return array();
-       }
-
-       /**
-        * @see FileBackend::clearCache()
-        */
-       final public function clearCache( array $paths = null ) {
-               if ( is_array( $paths ) ) {
-                       $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
-                       $paths = array_filter( $paths, 'strlen' ); // remove nulls
-               }
-               if ( $paths === null ) {
-                       $this->cheapCache->clear();
-                       $this->expensiveCache->clear();
-               } else {
-                       foreach ( $paths as $path ) {
-                               $this->cheapCache->clear( $path );
-                               $this->expensiveCache->clear( $path );
-                       }
-               }
-               $this->doClearCache( $paths );
-       }
-
-       /**
-        * Clears any additional stat caches for storage paths
-        *
-        * @see FileBackend::clearCache()
-        *
-        * @param $paths Array Storage paths (optional)
-        * @return void
-        */
-       protected function doClearCache( array $paths = null ) {}
-
-       /**
-        * Is this a key/value store where directories are just virtual?
-        * Virtual directories exists in so much as files exists that are
-        * prefixed with the directory path followed by a forward slash.
-        *
-        * @return bool
-        */
-       abstract protected function directoriesAreVirtual();
-
-       /**
-        * Check if a container name is valid.
-        * This checks for for length and illegal characters.
-        *
-        * @param $container string
-        * @return bool
-        */
-       final protected static function isValidContainerName( $container ) {
-               // This accounts for Swift and S3 restrictions while leaving room
-               // for things like '.xxx' (hex shard chars) or '.seg' (segments).
-               // This disallows directory separators or traversal characters.
-               // Note that matching strings URL encode to the same string;
-               // in Swift, the length restriction is *after* URL encoding.
-               return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container );
-       }
-
-       /**
-        * Splits a storage path into an internal container name,
-        * an internal relative file name, and a container shard suffix.
-        * Any shard suffix is already appended to the internal container name.
-        * This also checks that the storage path is valid and within this backend.
-        *
-        * If the container is sharded but a suffix could not be determined,
-        * this means that the path can only refer to a directory and can only
-        * be scanned by looking in all the container shards.
-        *
-        * @param $storagePath string
-        * @return Array (container, path, container suffix) or (null, null, null) if invalid
-        */
-       final protected function resolveStoragePath( $storagePath ) {
-               list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
-               if ( $backend === $this->name ) { // must be for this backend
-                       $relPath = self::normalizeContainerPath( $relPath );
-                       if ( $relPath !== null ) {
-                               // Get shard for the normalized path if this container is sharded
-                               $cShard = $this->getContainerShard( $container, $relPath );
-                               // Validate and sanitize the relative path (backend-specific)
-                               $relPath = $this->resolveContainerPath( $container, $relPath );
-                               if ( $relPath !== null ) {
-                                       // Prepend any wiki ID prefix to the container name
-                                       $container = $this->fullContainerName( $container );
-                                       if ( self::isValidContainerName( $container ) ) {
-                                               // Validate and sanitize the container name (backend-specific)
-                                               $container = $this->resolveContainerName( "{$container}{$cShard}" );
-                                               if ( $container !== null ) {
-                                                       return array( $container, $relPath, $cShard );
-                                               }
-                                       }
-                               }
-                       }
-               }
-               return array( null, null, null );
-       }
-
-       /**
-        * Like resolveStoragePath() except null values are returned if
-        * the container is sharded and the shard could not be determined.
-        *
-        * @see FileBackendStore::resolveStoragePath()
-        *
-        * @param $storagePath string
-        * @return Array (container, path) or (null, null) if invalid
-        */
-       final protected function resolveStoragePathReal( $storagePath ) {
-               list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
-               if ( $cShard !== null ) {
-                       return array( $container, $relPath );
-               }
-               return array( null, null );
-       }
-
-       /**
-        * Get the container name shard suffix for a given path.
-        * Any empty suffix means the container is not sharded.
-        *
-        * @param $container string Container name
-        * @param $relPath string Storage path relative to the container
-        * @return string|null Returns null if shard could not be determined
-        */
-       final protected function getContainerShard( $container, $relPath ) {
-               list( $levels, $base, $repeat ) = $this->getContainerHashLevels( $container );
-               if ( $levels == 1 || $levels == 2 ) {
-                       // Hash characters are either base 16 or 36
-                       $char = ( $base == 36 ) ? '[0-9a-z]' : '[0-9a-f]';
-                       // Get a regex that represents the shard portion of paths.
-                       // The concatenation of the captures gives us the shard.
-                       if ( $levels === 1 ) { // 16 or 36 shards per container
-                               $hashDirRegex = '(' . $char . ')';
-                       } else { // 256 or 1296 shards per container
-                               if ( $repeat ) { // verbose hash dir format (e.g. "a/ab/abc")
-                                       $hashDirRegex = $char . '/(' . $char . '{2})';
-                               } else { // short hash dir format (e.g. "a/b/c")
-                                       $hashDirRegex = '(' . $char . ')/(' . $char . ')';
-                               }
-                       }
-                       // Allow certain directories to be above the hash dirs so as
-                       // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab").
-                       // They must be 2+ chars to avoid any hash directory ambiguity.
-                       $m = array();
-                       if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) {
-                               return '.' . implode( '', array_slice( $m, 1 ) );
-                       }
-                       return null; // failed to match
-               }
-               return ''; // no sharding
-       }
-
-       /**
-        * Check if a storage path maps to a single shard.
-        * Container dirs like "a", where the container shards on "x/xy",
-        * can reside on several shards. Such paths are tricky to handle.
-        *
-        * @param $storagePath string Storage path
-        * @return bool
-        */
-       final public function isSingleShardPathInternal( $storagePath ) {
-               list( $c, $r, $shard ) = $this->resolveStoragePath( $storagePath );
-               return ( $shard !== null );
-       }
-
-       /**
-        * Get the sharding config for a container.
-        * If greater than 0, then all file storage paths within
-        * the container are required to be hashed accordingly.
-        *
-        * @param $container string
-        * @return Array (integer levels, integer base, repeat flag) or (0, 0, false)
-        */
-       final protected function getContainerHashLevels( $container ) {
-               if ( isset( $this->shardViaHashLevels[$container] ) ) {
-                       $config = $this->shardViaHashLevels[$container];
-                       $hashLevels = (int)$config['levels'];
-                       if ( $hashLevels == 1 || $hashLevels == 2 ) {
-                               $hashBase = (int)$config['base'];
-                               if ( $hashBase == 16 || $hashBase == 36 ) {
-                                       return array( $hashLevels, $hashBase, $config['repeat'] );
-                               }
-                       }
-               }
-               return array( 0, 0, false ); // no sharding
-       }
-
-       /**
-        * Get a list of full container shard suffixes for a container
-        *
-        * @param $container string
-        * @return Array
-        */
-       final protected function getContainerSuffixes( $container ) {
-               $shards = array();
-               list( $digits, $base ) = $this->getContainerHashLevels( $container );
-               if ( $digits > 0 ) {
-                       $numShards = pow( $base, $digits );
-                       for ( $index = 0; $index < $numShards; $index++ ) {
-                               $shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits );
-                       }
-               }
-               return $shards;
-       }
-
-       /**
-        * Get the full container name, including the wiki ID prefix
-        *
-        * @param $container string
-        * @return string
-        */
-       final protected function fullContainerName( $container ) {
-               if ( $this->wikiId != '' ) {
-                       return "{$this->wikiId}-$container";
-               } else {
-                       return $container;
-               }
-       }
-
-       /**
-        * Resolve a container name, checking if it's allowed by the backend.
-        * This is intended for internal use, such as encoding illegal chars.
-        * Subclasses can override this to be more restrictive.
-        *
-        * @param $container string
-        * @return string|null
-        */
-       protected function resolveContainerName( $container ) {
-               return $container;
-       }
-
-       /**
-        * Resolve a relative storage path, checking if it's allowed by the backend.
-        * This is intended for internal use, such as encoding illegal chars or perhaps
-        * getting absolute paths (e.g. FS based backends). Note that the relative path
-        * may be the empty string (e.g. the path is simply to the container).
-        *
-        * @param $container string Container name
-        * @param $relStoragePath string Storage path relative to the container
-        * @return string|null Path or null if not valid
-        */
-       protected function resolveContainerPath( $container, $relStoragePath ) {
-               return $relStoragePath;
-       }
-
-       /**
-        * Get the cache key for a container
-        *
-        * @param $container string Resolved container name
-        * @return string
-        */
-       private function containerCacheKey( $container ) {
-               return wfMemcKey( 'backend', $this->getName(), 'container', $container );
-       }
-
-       /**
-        * Set the cached info for a container
-        *
-        * @param $container string Resolved container name
-        * @param $val mixed Information to cache
-        */
-       final protected function setContainerCache( $container, $val ) {
-               $this->memCache->add( $this->containerCacheKey( $container ), $val, 14*86400 );
-       }
-
-       /**
-        * Delete the cached info for a container.
-        * The cache key is salted for a while to prevent race conditions.
-        *
-        * @param $container string Resolved container name
-        */
-       final protected function deleteContainerCache( $container ) {
-               if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) {
-                       trigger_error( "Unable to delete stat cache for container $container." );
-               }
-       }
-
-       /**
-        * Do a batch lookup from cache for container stats for all containers
-        * used in a list of container names, storage paths, or FileOp objects.
-        *
-        * @param $items Array
-        * @return void
-        */
-       final protected function primeContainerCache( array $items ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-
-               $paths = array(); // list of storage paths
-               $contNames = array(); // (cache key => resolved container name)
-               // Get all the paths/containers from the items...
-               foreach ( $items as $item ) {
-                       if ( $item instanceof FileOp ) {
-                               $paths = array_merge( $paths, $item->storagePathsRead() );
-                               $paths = array_merge( $paths, $item->storagePathsChanged() );
-                       } elseif ( self::isStoragePath( $item ) ) {
-                               $paths[] = $item;
-                       } elseif ( is_string( $item ) ) { // full container name
-                               $contNames[$this->containerCacheKey( $item )] = $item;
-                       }
-               }
-               // Get all the corresponding cache keys for paths...
-               foreach ( $paths as $path ) {
-                       list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path );
-                       if ( $fullCont !== null ) { // valid path for this backend
-                               $contNames[$this->containerCacheKey( $fullCont )] = $fullCont;
-                       }
-               }
-
-               $contInfo = array(); // (resolved container name => cache value)
-               // Get all cache entries for these container cache keys...
-               $values = $this->memCache->getMulti( array_keys( $contNames ) );
-               foreach ( $values as $cacheKey => $val ) {
-                       $contInfo[$contNames[$cacheKey]] = $val;
-               }
-
-               // Populate the container process cache for the backend...
-               $this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Fill the backend-specific process cache given an array of
-        * resolved container names and their corresponding cached info.
-        * Only containers that actually exist should appear in the map.
-        *
-        * @param $containerInfo Array Map of resolved container names to cached info
-        * @return void
-        */
-       protected function doPrimeContainerCache( array $containerInfo ) {}
-
-       /**
-        * Get the cache key for a file path
-        *
-        * @param $path string Storage path
-        * @return string
-        */
-       private function fileCacheKey( $path ) {
-               return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) );
-       }
-
-       /**
-        * Set the cached stat info for a file path
-        *
-        * @param $path string Storage path
-        * @param $val mixed Information to cache
-        */
-       final protected function setFileCache( $path, $val ) {
-               $this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 );
-       }
-
-       /**
-        * Delete the cached stat info for a file path.
-        * The cache key is salted for a while to prevent race conditions.
-        *
-        * @param $path string Storage path
-        */
-       final protected function deleteFileCache( $path ) {
-               if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) {
-                       trigger_error( "Unable to delete stat cache for file $path." );
-               }
-       }
-
-       /**
-        * Do a batch lookup from cache for file stats for all paths
-        * used in a list of storage paths or FileOp objects.
-        *
-        * @param $items Array List of storage paths or FileOps
-        * @return void
-        */
-       final protected function primeFileCache( array $items ) {
-               wfProfileIn( __METHOD__ );
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-
-               $paths = array(); // list of storage paths
-               $pathNames = array(); // (cache key => storage path)
-               // Get all the paths/containers from the items...
-               foreach ( $items as $item ) {
-                       if ( $item instanceof FileOp ) {
-                               $paths = array_merge( $paths, $item->storagePathsRead() );
-                               $paths = array_merge( $paths, $item->storagePathsChanged() );
-                       } elseif ( self::isStoragePath( $item ) ) {
-                               $paths[] = $item;
-                       }
-               }
-               // Get all the corresponding cache keys for paths...
-               foreach ( $paths as $path ) {
-                       list( $cont, $rel, $s ) = $this->resolveStoragePath( $path );
-                       if ( $rel !== null ) { // valid path for this backend
-                               $pathNames[$this->fileCacheKey( $path )] = $path;
-                       }
-               }
-               // Get all cache entries for these container cache keys...
-               $values = $this->memCache->getMulti( array_keys( $pathNames ) );
-               foreach ( $values as $cacheKey => $val ) {
-                       if ( is_array( $val ) ) {
-                               $path = $pathNames[$cacheKey];
-                               $this->cheapCache->set( $path, 'stat', $val );
-                               if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
-                                       $this->cheapCache->set( $path, 'sha1',
-                                               array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) );
-                               }
-                       }
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               wfProfileOut( __METHOD__ );
-       }
-}
-
-/**
- * FileBackendStore helper class for performing asynchronous file operations.
- *
- * For example, calling FileBackendStore::createInternal() with the "async"
- * param flag may result in a Status that contains this object as a value.
- * This class is largely backend-specific and is mostly just "magic" to be
- * passed to FileBackendStore::executeOpHandlesInternal().
- */
-abstract class FileBackendStoreOpHandle {
-       /** @var Array */
-       public $params = array(); // params to caller functions
-       /** @var FileBackendStore */
-       public $backend;
-       /** @var Array */
-       public $resourcesToClose = array();
-
-       public $call; // string; name that identifies the function called
-
-       /**
-        * Close all open file handles
-        *
-        * @return void
-        */
-       public function closeResources() {
-               array_map( 'fclose', $this->resourcesToClose );
-       }
-}
-
-/**
- * FileBackendStore helper function to handle listings that span container shards.
- * Do not use this class from places outside of FileBackendStore.
- *
- * @ingroup FileBackend
- */
-abstract class FileBackendStoreShardListIterator implements Iterator {
-       /** @var FileBackendStore */
-       protected $backend;
-       /** @var Array */
-       protected $params;
-       /** @var Array */
-       protected $shardSuffixes;
-       protected $container; // string; full container name
-       protected $directory; // string; resolved relative path
-
-       /** @var Traversable */
-       protected $iter;
-       protected $curShard = 0; // integer
-       protected $pos = 0; // integer
-
-       /** @var Array */
-       protected $multiShardPaths = array(); // (rel path => 1)
-
-       /**
-        * @param $backend FileBackendStore
-        * @param $container string Full storage container name
-        * @param $dir string Storage directory relative to container
-        * @param $suffixes Array List of container shard suffixes
-        * @param $params Array
-        */
-       public function __construct(
-               FileBackendStore $backend, $container, $dir, array $suffixes, array $params
-       ) {
-               $this->backend = $backend;
-               $this->container = $container;
-               $this->directory = $dir;
-               $this->shardSuffixes = $suffixes;
-               $this->params = $params;
-       }
-
-       /**
-        * @see Iterator::key()
-        * @return integer
-        */
-       public function key() {
-               return $this->pos;
-       }
-
-       /**
-        * @see Iterator::valid()
-        * @return bool
-        */
-       public function valid() {
-               if ( $this->iter instanceof Iterator ) {
-                       return $this->iter->valid();
-               } elseif ( is_array( $this->iter ) ) {
-                       return ( current( $this->iter ) !== false ); // no paths can have this value
-               }
-               return false; // some failure?
-       }
-
-       /**
-        * @see Iterator::current()
-        * @return string|bool String or false
-        */
-       public function current() {
-               return ( $this->iter instanceof Iterator )
-                       ? $this->iter->current()
-                       : current( $this->iter );
-       }
-
-       /**
-        * @see Iterator::next()
-        * @return void
-        */
-       public function next() {
-               ++$this->pos;
-               ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
-               do {
-                       $continue = false; // keep scanning shards?
-                       $this->filterViaNext(); // filter out duplicates
-                       // Find the next non-empty shard if no elements are left
-                       if ( !$this->valid() ) {
-                               $this->nextShardIteratorIfNotValid();
-                               $continue = $this->valid(); // re-filter unless we ran out of shards
-                       }
-               } while ( $continue );
-       }
-
-       /**
-        * @see Iterator::rewind()
-        * @return void
-        */
-       public function rewind() {
-               $this->pos = 0;
-               $this->curShard = 0;
-               $this->setIteratorFromCurrentShard();
-               do {
-                       $continue = false; // keep scanning shards?
-                       $this->filterViaNext(); // filter out duplicates
-                       // Find the next non-empty shard if no elements are left
-                       if ( !$this->valid() ) {
-                               $this->nextShardIteratorIfNotValid();
-                               $continue = $this->valid(); // re-filter unless we ran out of shards
-                       }
-               } while ( $continue );
-       }
-
-       /**
-        * Filter out duplicate items by advancing to the next ones
-        */
-       protected function filterViaNext() {
-               while ( $this->valid() ) {
-                       $rel = $this->iter->current(); // path relative to given directory
-                       $path = $this->params['dir'] . "/{$rel}"; // full storage path
-                       if ( $this->backend->isSingleShardPathInternal( $path ) ) {
-                               break; // path is only on one shard; no issue with duplicates
-                       } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
-                               // Don't keep listing paths that are on multiple shards
-                               ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
-                       } else {
-                               $this->multiShardPaths[$rel] = 1;
-                               break;
-                       }
-               }
-       }
-
-       /**
-        * If the list iterator for this container shard is out of items,
-        * then move on to the next container that has items.
-        * If there are none, then it advances to the last container.
-        */
-       protected function nextShardIteratorIfNotValid() {
-               while ( !$this->valid() && ++$this->curShard < count( $this->shardSuffixes ) ) {
-                       $this->setIteratorFromCurrentShard();
-               }
-       }
-
-       /**
-        * Set the list iterator to that of the current container shard
-        */
-       protected function setIteratorFromCurrentShard() {
-               $this->iter = $this->listFromShard(
-                       $this->container . $this->shardSuffixes[$this->curShard],
-                       $this->directory, $this->params );
-               // Start loading results so that current() works
-               if ( $this->iter ) {
-                       ( $this->iter instanceof Iterator ) ? $this->iter->rewind() : reset( $this->iter );
-               }
-       }
-
-       /**
-        * Get the list for a given container shard
-        *
-        * @param $container string Resolved container name
-        * @param $dir string Resolved path relative to container
-        * @param $params Array
-        * @return Traversable|Array|null
-        */
-       abstract protected function listFromShard( $container, $dir, array $params );
-}
-
-/**
- * Iterator for listing directories
- */
-class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
-       /**
-        * @see FileBackendStoreShardListIterator::listFromShard()
-        * @return Array|null|Traversable
-        */
-       protected function listFromShard( $container, $dir, array $params ) {
-               return $this->backend->getDirectoryListInternal( $container, $dir, $params );
-       }
-}
-
-/**
- * Iterator for listing regular files
- */
-class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
-       /**
-        * @see FileBackendStoreShardListIterator::listFromShard()
-        * @return Array|null|Traversable
-        */
-       protected function listFromShard( $container, $dir, array $params ) {
-               return $this->backend->getFileListInternal( $container, $dir, $params );
-       }
-}
diff --git a/includes/filerepo/backend/FileOp.php b/includes/filerepo/backend/FileOp.php
deleted file mode 100644 (file)
index fa87c3a..0000000
+++ /dev/null
@@ -1,778 +0,0 @@
-<?php
-/**
- * Helper class for representing operations with transaction support.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * FileBackend helper class for representing operations.
- * Do not use this class from places outside FileBackend.
- *
- * Methods called from FileOpBatch::attempt() should avoid throwing
- * exceptions at all costs. FileOp objects should be lightweight in order
- * to support large arrays in memory and serialization.
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-abstract class FileOp {
-       /** @var Array */
-       protected $params = array();
-       /** @var FileBackendStore */
-       protected $backend;
-
-       protected $state = self::STATE_NEW; // integer
-       protected $failed = false; // boolean
-       protected $async = false; // boolean
-       protected $useLatest = true; // boolean
-       protected $batchId; // string
-
-       protected $sourceSha1; // string
-       protected $destSameAsSource; // boolean
-
-       /* Object life-cycle */
-       const STATE_NEW = 1;
-       const STATE_CHECKED = 2;
-       const STATE_ATTEMPTED = 3;
-
-       /**
-        * Build a new file operation transaction
-        *
-        * @param $backend FileBackendStore
-        * @param $params Array
-        * @throws MWException
-        */
-       final public function __construct( FileBackendStore $backend, array $params ) {
-               $this->backend = $backend;
-               list( $required, $optional ) = $this->allowedParams();
-               foreach ( $required as $name ) {
-                       if ( isset( $params[$name] ) ) {
-                               $this->params[$name] = $params[$name];
-                       } else {
-                               throw new MWException( "File operation missing parameter '$name'." );
-                       }
-               }
-               foreach ( $optional as $name ) {
-                       if ( isset( $params[$name] ) ) {
-                               $this->params[$name] = $params[$name];
-                       }
-               }
-               $this->params = $params;
-       }
-
-       /**
-        * Set the batch UUID this operation belongs to
-        *
-        * @param $batchId string
-        * @return void
-        */
-       final public function setBatchId( $batchId ) {
-               $this->batchId = $batchId;
-       }
-
-       /**
-        * Whether to allow stale data for file reads and stat checks
-        *
-        * @param $allowStale bool
-        * @return void
-        */
-       final public function allowStaleReads( $allowStale ) {
-               $this->useLatest = !$allowStale;
-       }
-
-       /**
-        * Get the value of the parameter with the given name
-        *
-        * @param $name string
-        * @return mixed Returns null if the parameter is not set
-        */
-       final public function getParam( $name ) {
-               return isset( $this->params[$name] ) ? $this->params[$name] : null;
-       }
-
-       /**
-        * Check if this operation failed precheck() or attempt()
-        *
-        * @return bool
-        */
-       final public function failed() {
-               return $this->failed;
-       }
-
-       /**
-        * Get a new empty predicates array for precheck()
-        *
-        * @return Array
-        */
-       final public static function newPredicates() {
-               return array( 'exists' => array(), 'sha1' => array() );
-       }
-
-       /**
-        * Get a new empty dependency tracking array for paths read/written to
-        *
-        * @return Array
-        */
-       final public static function newDependencies() {
-               return array( 'read' => array(), 'write' => array() );
-       }
-
-       /**
-        * Update a dependency tracking array to account for this operation
-        *
-        * @param $deps Array Prior path reads/writes; format of FileOp::newPredicates()
-        * @return Array
-        */
-       final public function applyDependencies( array $deps ) {
-               $deps['read']  += array_fill_keys( $this->storagePathsRead(), 1 );
-               $deps['write'] += array_fill_keys( $this->storagePathsChanged(), 1 );
-               return $deps;
-       }
-
-       /**
-        * Check if this operation changes files listed in $paths
-        *
-        * @param $paths Array Prior path reads/writes; format of FileOp::newPredicates()
-        * @return boolean
-        */
-       final public function dependsOn( array $deps ) {
-               foreach ( $this->storagePathsChanged() as $path ) {
-                       if ( isset( $deps['read'][$path] ) || isset( $deps['write'][$path] ) ) {
-                               return true; // "output" or "anti" dependency
-                       }
-               }
-               foreach ( $this->storagePathsRead() as $path ) {
-                       if ( isset( $deps['write'][$path] ) ) {
-                               return true; // "flow" dependency
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Get the file journal entries for this file operation
-        *
-        * @param $oPredicates Array Pre-op info about files (format of FileOp::newPredicates)
-        * @param $nPredicates Array Post-op info about files (format of FileOp::newPredicates)
-        * @return Array
-        */
-       final public function getJournalEntries( array $oPredicates, array $nPredicates ) {
-               $nullEntries = array();
-               $updateEntries = array();
-               $deleteEntries = array();
-               $pathsUsed = array_merge( $this->storagePathsRead(), $this->storagePathsChanged() );
-               foreach ( $pathsUsed as $path ) {
-                       $nullEntries[] = array( // assertion for recovery
-                               'op'      => 'null',
-                               'path'    => $path,
-                               'newSha1' => $this->fileSha1( $path, $oPredicates )
-                       );
-               }
-               foreach ( $this->storagePathsChanged() as $path ) {
-                       if ( $nPredicates['sha1'][$path] === false ) { // deleted
-                               $deleteEntries[] = array(
-                                       'op'      => 'delete',
-                                       'path'    => $path,
-                                       'newSha1' => ''
-                               );
-                       } else { // created/updated
-                               $updateEntries[] = array(
-                                       'op'      => $this->fileExists( $path, $oPredicates ) ? 'update' : 'create',
-                                       'path'    => $path,
-                                       'newSha1' => $nPredicates['sha1'][$path]
-                               );
-                       }
-               }
-               return array_merge( $nullEntries, $updateEntries, $deleteEntries );
-       }
-
-       /**
-        * Check preconditions of the operation without writing anything
-        *
-        * @param $predicates Array
-        * @return Status
-        */
-       final public function precheck( array &$predicates ) {
-               if ( $this->state !== self::STATE_NEW ) {
-                       return Status::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state );
-               }
-               $this->state = self::STATE_CHECKED;
-               $status = $this->doPrecheck( $predicates );
-               if ( !$status->isOK() ) {
-                       $this->failed = true;
-               }
-               return $status;
-       }
-
-       /**
-        * @return Status
-        */
-       protected function doPrecheck( array &$predicates ) {
-               return Status::newGood();
-       }
-
-       /**
-        * Attempt the operation
-        *
-        * @return Status
-        */
-       final public function attempt() {
-               if ( $this->state !== self::STATE_CHECKED ) {
-                       return Status::newFatal( 'fileop-fail-state', self::STATE_CHECKED, $this->state );
-               } elseif ( $this->failed ) { // failed precheck
-                       return Status::newFatal( 'fileop-fail-attempt-precheck' );
-               }
-               $this->state = self::STATE_ATTEMPTED;
-               $status = $this->doAttempt();
-               if ( !$status->isOK() ) {
-                       $this->failed = true;
-                       $this->logFailure( 'attempt' );
-               }
-               return $status;
-       }
-
-       /**
-        * @return Status
-        */
-       protected function doAttempt() {
-               return Status::newGood();
-       }
-
-       /**
-        * Attempt the operation in the background
-        *
-        * @return Status
-        */
-       final public function attemptAsync() {
-               $this->async = true;
-               $result = $this->attempt();
-               $this->async = false;
-               return $result;
-       }
-
-       /**
-        * Get the file operation parameters
-        *
-        * @return Array (required params list, optional params list)
-        */
-       protected function allowedParams() {
-               return array( array(), array() );
-       }
-
-       /**
-        * Adjust params to FileBackendStore internal file calls
-        *
-        * @param $params Array
-        * @return Array (required params list, optional params list)
-        */
-       protected function setFlags( array $params ) {
-               return array( 'async' => $this->async ) + $params;
-       }
-
-       /**
-        * Get a list of storage paths read from for this operation
-        *
-        * @return Array
-        */
-       final public function storagePathsRead() {
-               return array_map( 'FileBackend::normalizeStoragePath', $this->doStoragePathsRead() );
-       }
-
-       /**
-        * @see FileOp::storagePathsRead()
-        * @return Array
-        */
-       protected function doStoragePathsRead() {
-               return array();
-       }
-
-       /**
-        * Get a list of storage paths written to for this operation
-        *
-        * @return Array
-        */
-       final public function storagePathsChanged() {
-               return array_map( 'FileBackend::normalizeStoragePath', $this->doStoragePathsChanged() );
-       }
-
-       /**
-        * @see FileOp::storagePathsChanged()
-        * @return Array
-        */
-       protected function doStoragePathsChanged() {
-               return array();
-       }
-
-       /**
-        * Check for errors with regards to the destination file already existing.
-        * This also updates the destSameAsSource and sourceSha1 member variables.
-        * A bad status will be returned if there is no chance it can be overwritten.
-        *
-        * @param $predicates Array
-        * @return Status
-        */
-       protected function precheckDestExistence( array $predicates ) {
-               $status = Status::newGood();
-               // Get hash of source file/string and the destination file
-               $this->sourceSha1 = $this->getSourceSha1Base36(); // FS file or data string
-               if ( $this->sourceSha1 === null ) { // file in storage?
-                       $this->sourceSha1 = $this->fileSha1( $this->params['src'], $predicates );
-               }
-               $this->destSameAsSource = false;
-               if ( $this->fileExists( $this->params['dst'], $predicates ) ) {
-                       if ( $this->getParam( 'overwrite' ) ) {
-                               return $status; // OK
-                       } elseif ( $this->getParam( 'overwriteSame' ) ) {
-                               $dhash = $this->fileSha1( $this->params['dst'], $predicates );
-                               // Check if hashes are valid and match each other...
-                               if ( !strlen( $this->sourceSha1 ) || !strlen( $dhash ) ) {
-                                       $status->fatal( 'backend-fail-hashes' );
-                               } elseif ( $this->sourceSha1 !== $dhash ) {
-                                       // Give an error if the files are not identical
-                                       $status->fatal( 'backend-fail-notsame', $this->params['dst'] );
-                               } else {
-                                       $this->destSameAsSource = true; // OK
-                               }
-                               return $status; // do nothing; either OK or bad status
-                       } else {
-                               $status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
-                               return $status;
-                       }
-               }
-               return $status;
-       }
-
-       /**
-        * precheckDestExistence() helper function to get the source file SHA-1.
-        * Subclasses should overwride this iff the source is not in storage.
-        *
-        * @return string|bool Returns false on failure
-        */
-       protected function getSourceSha1Base36() {
-               return null; // N/A
-       }
-
-       /**
-        * Check if a file will exist in storage when this operation is attempted
-        *
-        * @param $source string Storage path
-        * @param $predicates Array
-        * @return bool
-        */
-       final protected function fileExists( $source, array $predicates ) {
-               if ( isset( $predicates['exists'][$source] ) ) {
-                       return $predicates['exists'][$source]; // previous op assures this
-               } else {
-                       $params = array( 'src' => $source, 'latest' => $this->useLatest );
-                       return $this->backend->fileExists( $params );
-               }
-       }
-
-       /**
-        * Get the SHA-1 of a file in storage when this operation is attempted
-        *
-        * @param $source string Storage path
-        * @param $predicates Array
-        * @return string|bool False on failure
-        */
-       final protected function fileSha1( $source, array $predicates ) {
-               if ( isset( $predicates['sha1'][$source] ) ) {
-                       return $predicates['sha1'][$source]; // previous op assures this
-               } else {
-                       $params = array( 'src' => $source, 'latest' => $this->useLatest );
-                       return $this->backend->getFileSha1Base36( $params );
-               }
-       }
-
-       /**
-        * Get the backend this operation is for
-        *
-        * @return FileBackendStore
-        */
-       public function getBackend() {
-               return $this->backend;
-       }
-
-       /**
-        * Log a file operation failure and preserve any temp files
-        *
-        * @param $action string
-        * @return void
-        */
-       final public function logFailure( $action ) {
-               $params = $this->params;
-               $params['failedAction'] = $action;
-               try {
-                       wfDebugLog( 'FileOperation', get_class( $this ) .
-                               " failed (batch #{$this->batchId}): " . FormatJson::encode( $params ) );
-               } catch ( Exception $e ) {
-                       // bad config? debug log error?
-               }
-       }
-}
-
-/**
- * Store a file into the backend from a file on the file system.
- * Parameters similar to FileBackendStore::storeInternal(), which include:
- *   - src           : source path on file system
- *   - dst           : destination storage path
- *   - overwrite     : do nothing and pass if an identical file exists at destination
- *   - overwriteSame : override any existing file at destination
- */
-class StoreFileOp extends FileOp {
-       /**
-        * @return array
-        */
-       protected function allowedParams() {
-               return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
-       }
-
-       /**
-        * @param $predicates array
-        * @return Status
-        */
-       protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
-               // Check if the source file exists on the file system
-               if ( !is_file( $this->params['src'] ) ) {
-                       $status->fatal( 'backend-fail-notexists', $this->params['src'] );
-                       return $status;
-               // Check if the source file is too big
-               } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
-                       $status->fatal( 'backend-fail-maxsize',
-                               $this->params['dst'], $this->backend->maxFileSizeInternal() );
-                       $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
-                       return $status;
-               // Check if a file can be placed at the destination
-               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
-                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
-                       $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
-                       return $status;
-               }
-               // Check if destination file exists
-               $status->merge( $this->precheckDestExistence( $predicates ) );
-               if ( $status->isOK() ) {
-                       // Update file existence predicates
-                       $predicates['exists'][$this->params['dst']] = true;
-                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
-               }
-               return $status; // safe to call attempt()
-       }
-
-       /**
-        * @return Status
-        */
-       protected function doAttempt() {
-               // Store the file at the destination
-               if ( !$this->destSameAsSource ) {
-                       return $this->backend->storeInternal( $this->setFlags( $this->params ) );
-               }
-               return Status::newGood();
-       }
-
-       /**
-        * @return bool|string
-        */
-       protected function getSourceSha1Base36() {
-               wfSuppressWarnings();
-               $hash = sha1_file( $this->params['src'] );
-               wfRestoreWarnings();
-               if ( $hash !== false ) {
-                       $hash = wfBaseConvert( $hash, 16, 36, 31 );
-               }
-               return $hash;
-       }
-
-       protected function doStoragePathsChanged() {
-               return array( $this->params['dst'] );
-       }
-}
-
-/**
- * Create a file in the backend with the given content.
- * Parameters similar to FileBackendStore::createInternal(), which include:
- *   - content       : the raw file contents
- *   - dst           : destination storage path
- *   - overwrite     : do nothing and pass if an identical file exists at destination
- *   - overwriteSame : override any existing file at destination
- */
-class CreateFileOp extends FileOp {
-       protected function allowedParams() {
-               return array( array( 'content', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
-       }
-
-       protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
-               // Check if the source data is too big
-               if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
-                       $status->fatal( 'backend-fail-maxsize',
-                               $this->params['dst'], $this->backend->maxFileSizeInternal() );
-                       $status->fatal( 'backend-fail-create', $this->params['dst'] );
-                       return $status;
-               // Check if a file can be placed at the destination
-               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
-                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
-                       $status->fatal( 'backend-fail-create', $this->params['dst'] );
-                       return $status;
-               }
-               // Check if destination file exists
-               $status->merge( $this->precheckDestExistence( $predicates ) );
-               if ( $status->isOK() ) {
-                       // Update file existence predicates
-                       $predicates['exists'][$this->params['dst']] = true;
-                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
-               }
-               return $status; // safe to call attempt()
-       }
-
-       /**
-        * @return Status
-        */
-       protected function doAttempt() {
-               if ( !$this->destSameAsSource ) {
-                       // Create the file at the destination
-                       return $this->backend->createInternal( $this->setFlags( $this->params ) );
-               }
-               return Status::newGood();
-       }
-
-       /**
-        * @return bool|String
-        */
-       protected function getSourceSha1Base36() {
-               return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
-       }
-
-       /**
-        * @return array
-        */
-       protected function doStoragePathsChanged() {
-               return array( $this->params['dst'] );
-       }
-}
-
-/**
- * Copy a file from one storage path to another in the backend.
- * Parameters similar to FileBackendStore::copyInternal(), which include:
- *   - src           : source storage path
- *   - dst           : destination storage path
- *   - overwrite     : do nothing and pass if an identical file exists at destination
- *   - overwriteSame : override any existing file at destination
- */
-class CopyFileOp extends FileOp {
-       /**
-        * @return array
-        */
-       protected function allowedParams() {
-               return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
-       }
-
-       /**
-        * @param $predicates array
-        * @return Status
-        */
-       protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
-               // Check if the source file exists
-               if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
-                       $status->fatal( 'backend-fail-notexists', $this->params['src'] );
-                       return $status;
-               // Check if a file can be placed at the destination
-               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
-                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
-                       $status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
-                       return $status;
-               }
-               // Check if destination file exists
-               $status->merge( $this->precheckDestExistence( $predicates ) );
-               if ( $status->isOK() ) {
-                       // Update file existence predicates
-                       $predicates['exists'][$this->params['dst']] = true;
-                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
-               }
-               return $status; // safe to call attempt()
-       }
-
-       /**
-        * @return Status
-        */
-       protected function doAttempt() {
-               // Do nothing if the src/dst paths are the same
-               if ( $this->params['src'] !== $this->params['dst'] ) {
-                       // Copy the file into the destination
-                       if ( !$this->destSameAsSource ) {
-                               return $this->backend->copyInternal( $this->setFlags( $this->params ) );
-                       }
-               }
-               return Status::newGood();
-       }
-
-       /**
-        * @return array
-        */
-       protected function doStoragePathsRead() {
-               return array( $this->params['src'] );
-       }
-
-       /**
-        * @return array
-        */
-       protected function doStoragePathsChanged() {
-               return array( $this->params['dst'] );
-       }
-}
-
-/**
- * Move a file from one storage path to another in the backend.
- * Parameters similar to FileBackendStore::moveInternal(), which include:
- *   - src           : source storage path
- *   - dst           : destination storage path
- *   - overwrite     : do nothing and pass if an identical file exists at destination
- *   - overwriteSame : override any existing file at destination
- */
-class MoveFileOp extends FileOp {
-       /**
-        * @return array
-        */
-       protected function allowedParams() {
-               return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
-       }
-
-       /**
-        * @param $predicates array
-        * @return Status
-        */
-       protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
-               // Check if the source file exists
-               if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
-                       $status->fatal( 'backend-fail-notexists', $this->params['src'] );
-                       return $status;
-               // Check if a file can be placed at the destination
-               } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
-                       $status->fatal( 'backend-fail-usable', $this->params['dst'] );
-                       $status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
-                       return $status;
-               }
-               // Check if destination file exists
-               $status->merge( $this->precheckDestExistence( $predicates ) );
-               if ( $status->isOK() ) {
-                       // Update file existence predicates
-                       $predicates['exists'][$this->params['src']] = false;
-                       $predicates['sha1'][$this->params['src']] = false;
-                       $predicates['exists'][$this->params['dst']] = true;
-                       $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
-               }
-               return $status; // safe to call attempt()
-       }
-
-       /**
-        * @return Status
-        */
-       protected function doAttempt() {
-               // Do nothing if the src/dst paths are the same
-               if ( $this->params['src'] !== $this->params['dst'] ) {
-                       if ( !$this->destSameAsSource ) {
-                               // Move the file into the destination
-                               return $this->backend->moveInternal( $this->setFlags( $this->params ) );
-                       } else {
-                               // Just delete source as the destination needs no changes
-                               $params = array( 'src' => $this->params['src'] );
-                               return $this->backend->deleteInternal( $this->setFlags( $params ) );
-                       }
-               }
-               return Status::newGood();
-       }
-
-       /**
-        * @return array
-        */
-       protected function doStoragePathsRead() {
-               return array( $this->params['src'] );
-       }
-
-       /**
-        * @return array
-        */
-       protected function doStoragePathsChanged() {
-               return array( $this->params['src'], $this->params['dst'] );
-       }
-}
-
-/**
- * Delete a file at the given storage path from the backend.
- * Parameters similar to FileBackendStore::deleteInternal(), which include:
- *   - src                 : source storage path
- *   - ignoreMissingSource : don't return an error if the file does not exist
- */
-class DeleteFileOp extends FileOp {
-       /**
-        * @return array
-        */
-       protected function allowedParams() {
-               return array( array( 'src' ), array( 'ignoreMissingSource' ) );
-       }
-
-       protected $needsDelete = true;
-
-       /**
-        * @param array $predicates
-        * @return Status
-        */
-       protected function doPrecheck( array &$predicates ) {
-               $status = Status::newGood();
-               // Check if the source file exists
-               if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
-                       if ( !$this->getParam( 'ignoreMissingSource' ) ) {
-                               $status->fatal( 'backend-fail-notexists', $this->params['src'] );
-                               return $status;
-                       }
-                       $this->needsDelete = false;
-               }
-               // Update file existence predicates
-               $predicates['exists'][$this->params['src']] = false;
-               $predicates['sha1'][$this->params['src']] = false;
-               return $status; // safe to call attempt()
-       }
-
-       /**
-        * @return Status
-        */
-       protected function doAttempt() {
-               if ( $this->needsDelete ) {
-                       // Delete the source file
-                       return $this->backend->deleteInternal( $this->setFlags( $this->params ) );
-               }
-               return Status::newGood();
-       }
-
-       /**
-        * @return array
-        */
-       protected function doStoragePathsChanged() {
-               return array( $this->params['src'] );
-       }
-}
-
-/**
- * Placeholder operation that has no params and does nothing
- */
-class NullFileOp extends FileOp {}
diff --git a/includes/filerepo/backend/FileOpBatch.php b/includes/filerepo/backend/FileOpBatch.php
deleted file mode 100644 (file)
index fd83e2c..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-<?php
-/**
- * Helper class for representing batch file operations.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Aaron Schulz
- */
-
-/**
- * Helper class for representing batch file operations.
- * Do not use this class from places outside FileBackend.
- *
- * Methods should avoid throwing exceptions at all costs.
- *
- * @ingroup FileBackend
- * @since 1.20
- */
-class FileOpBatch {
-       /* Timeout related parameters */
-       const MAX_BATCH_SIZE = 1000; // integer
-
-       /**
-        * Attempt to perform a series of file operations.
-        * Callers are responsible for handling file locking.
-        *
-        * $opts is an array of options, including:
-        *   - force        : Errors that would normally cause a rollback do not.
-        *                    The remaining operations are still attempted if any fail.
-        *   - allowStale   : Don't require the latest available data.
-        *                    This can increase performance for non-critical writes.
-        *                    This has no effect unless the 'force' flag is set.
-        *   - nonJournaled : Don't log this operation batch in the file journal.
-        *   - concurrency  : Try to do this many operations in parallel when possible.
-        *
-        * The resulting Status will be "OK" unless:
-        *   - a) unexpected operation errors occurred (network partitions, disk full...)
-        *   - b) significant operation errors occured and 'force' was not set
-        *
-        * @param $performOps Array List of FileOp operations
-        * @param $opts Array Batch operation options
-        * @param $journal FileJournal Journal to log operations to
-        * @return Status
-        */
-       public static function attempt( array $performOps, array $opts, FileJournal $journal ) {
-               wfProfileIn( __METHOD__ );
-               $status = Status::newGood();
-
-               $n = count( $performOps );
-               if ( $n > self::MAX_BATCH_SIZE ) {
-                       $status->fatal( 'backend-fail-batchsize', $n, self::MAX_BATCH_SIZE );
-                       wfProfileOut( __METHOD__ );
-                       return $status;
-               }
-
-               $batchId = $journal->getTimestampedUUID();
-               $allowStale = !empty( $opts['allowStale'] );
-               $ignoreErrors = !empty( $opts['force'] );
-               $journaled = empty( $opts['nonJournaled'] );
-               $maxConcurrency = isset( $opts['concurrency'] ) ? $opts['concurrency'] : 1;
-
-               $entries = array(); // file journal entry list
-               $predicates = FileOp::newPredicates(); // account for previous ops in prechecks
-               $curBatch = array(); // concurrent FileOp sub-batch accumulation
-               $curBatchDeps = FileOp::newDependencies(); // paths used in FileOp sub-batch
-               $pPerformOps = array(); // ordered list of concurrent FileOp sub-batches
-               $lastBackend = null; // last op backend name
-               // Do pre-checks for each operation; abort on failure...
-               foreach ( $performOps as $index => $fileOp ) {
-                       $backendName = $fileOp->getBackend()->getName();
-                       $fileOp->setBatchId( $batchId ); // transaction ID
-                       $fileOp->allowStaleReads( $allowStale ); // consistency level
-                       // Decide if this op can be done concurrently within this sub-batch
-                       // or if a new concurrent sub-batch must be started after this one...
-                       if ( $fileOp->dependsOn( $curBatchDeps )
-                               || count( $curBatch ) >= $maxConcurrency
-                               || ( $backendName !== $lastBackend && count( $curBatch ) )
-                       ) {
-                               $pPerformOps[] = $curBatch; // push this batch
-                               $curBatch = array(); // start a new sub-batch
-                               $curBatchDeps = FileOp::newDependencies();
-                       }
-                       $lastBackend = $backendName;
-                       $curBatch[$index] = $fileOp; // keep index
-                       // Update list of affected paths in this batch
-                       $curBatchDeps = $fileOp->applyDependencies( $curBatchDeps );
-                       // Simulate performing the operation...
-                       $oldPredicates = $predicates;
-                       $subStatus = $fileOp->precheck( $predicates ); // updates $predicates
-                       $status->merge( $subStatus );
-                       if ( $subStatus->isOK() ) {
-                               if ( $journaled ) { // journal log entries
-                                       $entries = array_merge( $entries,
-                                               $fileOp->getJournalEntries( $oldPredicates, $predicates ) );
-                               }
-                       } else { // operation failed?
-                               $status->success[$index] = false;
-                               ++$status->failCount;
-                               if ( !$ignoreErrors ) {
-                                       wfProfileOut( __METHOD__ );
-                                       return $status; // abort
-                               }
-                       }
-               }
-               // Push the last sub-batch
-               if ( count( $curBatch ) ) {
-                       $pPerformOps[] = $curBatch;
-               }
-
-               // Log the operations in the file journal...
-               if ( count( $entries ) ) {
-                       $subStatus = $journal->logChangeBatch( $entries, $batchId );
-                       if ( !$subStatus->isOK() ) {
-                               wfProfileOut( __METHOD__ );
-                               return $subStatus; // abort
-                       }
-               }
-
-               if ( $ignoreErrors ) { // treat precheck() fatals as mere warnings
-                       $status->setResult( true, $status->value );
-               }
-
-               // Attempt each operation (in parallel if allowed and possible)...
-               if ( count( $pPerformOps ) < count( $performOps ) ) {
-                       self::runBatchParallel( $pPerformOps, $status );
-               } else {
-                       self::runBatchSeries( $performOps, $status );
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * Attempt a list of file operations in series.
-        * This will abort remaining ops on failure.
-        *
-        * @param $performOps Array
-        * @param $status Status
-        * @return bool Success
-        */
-       protected static function runBatchSeries( array $performOps, Status $status ) {
-               foreach ( $performOps as $index => $fileOp ) {
-                       if ( $fileOp->failed() ) {
-                               continue; // nothing to do
-                       }
-                       $subStatus = $fileOp->attempt();
-                       $status->merge( $subStatus );
-                       if ( $subStatus->isOK() ) {
-                               $status->success[$index] = true;
-                               ++$status->successCount;
-                       } else {
-                               $status->success[$index] = false;
-                               ++$status->failCount;
-                               // We can't continue (even with $ignoreErrors) as $predicates is wrong.
-                               // Log the remaining ops as failed for recovery...
-                               for ( $i = ($index + 1); $i < count( $performOps ); $i++ ) {
-                                       $performOps[$i]->logFailure( 'attempt_aborted' );
-                               }
-                               return false; // bail out
-                       }
-               }
-               return true;
-       }
-
-       /**
-        * Attempt a list of file operations sub-batches in series.
-        *
-        * The operations *in* each sub-batch will be done in parallel.
-        * The caller is responsible for making sure the operations
-        * within any given sub-batch do not depend on each other.
-        * This will abort remaining ops on failure.
-        *
-        * @param $pPerformOps Array
-        * @param $status Status
-        * @return bool Success
-        */
-       protected static function runBatchParallel( array $pPerformOps, Status $status ) {
-               $aborted = false;
-               foreach ( $pPerformOps as $performOpsBatch ) {
-                       if ( $aborted ) { // check batch op abort flag...
-                               // We can't continue (even with $ignoreErrors) as $predicates is wrong.
-                               // Log the remaining ops as failed for recovery...
-                               foreach ( $performOpsBatch as $i => $fileOp ) {
-                                       $performOpsBatch[$i]->logFailure( 'attempt_aborted' );
-                               }
-                               continue;
-                       }
-                       $statuses = array();
-                       $opHandles = array();
-                       // Get the backend; all sub-batch ops belong to a single backend
-                       $backend = reset( $performOpsBatch )->getBackend();
-                       // If attemptAsync() returns synchronously, it was either an
-                       // error Status or the backend just doesn't support async ops.
-                       foreach ( $performOpsBatch as $i => $fileOp ) {
-                               if ( !$fileOp->failed() ) { // failed => already has Status
-                                       $subStatus = $fileOp->attemptAsync();
-                                       if ( $subStatus->value instanceof FileBackendStoreOpHandle ) {
-                                               $opHandles[$i] = $subStatus->value; // deferred
-                                       } else {
-                                               $statuses[$i] = $subStatus; // done already
-                                       }
-                               }
-                       }
-                       // Try to do all the operations concurrently...
-                       $statuses = $statuses + $backend->executeOpHandlesInternal( $opHandles );
-                       // Marshall and merge all the responses (blocking)...
-                       foreach ( $performOpsBatch as $i => $fileOp ) {
-                               if ( !$fileOp->failed() ) { // failed => already has Status
-                                       $subStatus = $statuses[$i];
-                                       $status->merge( $subStatus );
-                                       if ( $subStatus->isOK() ) {
-                                               $status->success[$i] = true;
-                                               ++$status->successCount;
-                                       } else {
-                                               $status->success[$i] = false;
-                                               ++$status->failCount;
-                                               $aborted = true; // set abort flag; we can't continue
-                                       }
-                               }
-                       }
-               }
-               return $status;
-       }
-}
diff --git a/includes/filerepo/backend/SwiftFileBackend.php b/includes/filerepo/backend/SwiftFileBackend.php
deleted file mode 100644 (file)
index 5be86e6..0000000
+++ /dev/null
@@ -1,1430 +0,0 @@
-<?php
-/**
- * OpenStack Swift based file backend.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- * @author Russ Nelson
- * @author Aaron Schulz
- */
-
-/**
- * @brief Class for an OpenStack Swift based file backend.
- *
- * This requires the SwiftCloudFiles MediaWiki extension, which includes
- * the php-cloudfiles library (https://github.com/rackspace/php-cloudfiles).
- * php-cloudfiles requires the curl, fileinfo, and mb_string PHP extensions.
- *
- * Status messages should avoid mentioning the Swift account name.
- * Likewise, error suppression should be used to avoid path disclosure.
- *
- * @ingroup FileBackend
- * @since 1.19
- */
-class SwiftFileBackend extends FileBackendStore {
-       /** @var CF_Authentication */
-       protected $auth; // Swift authentication handler
-       protected $authTTL; // integer seconds
-       protected $swiftAnonUser; // string; username to handle unauthenticated requests
-       protected $swiftUseCDN; // boolean; whether CloudFiles CDN is enabled
-       protected $swiftCDNExpiry; // integer; how long to cache things in the CDN
-       protected $swiftCDNPurgable; // boolean; whether object CDN purging is enabled
-
-       protected $maxContCacheSize = 300; // integer; max containers with entries
-
-       /** @var CF_Connection */
-       protected $conn; // Swift connection handle
-       protected $connStarted = 0; // integer UNIX timestamp
-       protected $connContainers = array(); // container object cache
-       protected $connException; // CloudFiles exception
-
-       /**
-        * @see FileBackendStore::__construct()
-        * Additional $config params include:
-        *   - swiftAuthUrl       : Swift authentication server URL
-        *   - swiftUser          : Swift user used by MediaWiki (account:username)
-        *   - swiftKey           : Swift authentication key for the above user
-        *   - swiftAuthTTL       : Swift authentication TTL (seconds)
-        *   - swiftAnonUser      : Swift user used for end-user requests (account:username).
-        *                          If set, then views of public containers are assumed to go
-        *                          through this user. If not set, then public containers are
-        *                          accessible to unauthenticated requests via ".r:*" in the ACL.
-        *   - swiftUseCDN        : Whether a Cloud Files Content Delivery Network is set up
-        *   - swiftCDNExpiry     : How long (in seconds) to store content in the CDN.
-        *                          If files may likely change, this should probably not exceed
-        *                          a few days. For example, deletions may take this long to apply.
-        *                          If object purging is enabled, however, this is not an issue.
-        *   - swiftCDNPurgable   : Whether object purge requests are allowed by the CDN.
-        *   - shardViaHashLevels : Map of container names to sharding config with:
-        *                             - base   : base of hash characters, 16 or 36
-        *                             - levels : the number of hash levels (and digits)
-        *                             - repeat : hash subdirectories are prefixed with all the
-        *                                        parent hash directory names (e.g. "a/ab/abc")
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-               if ( !MWInit::classExists( 'CF_Constants' ) ) {
-                       throw new MWException( 'SwiftCloudFiles extension not installed.' );
-               }
-               // Required settings
-               $this->auth = new CF_Authentication(
-                       $config['swiftUser'],
-                       $config['swiftKey'],
-                       null, // account; unused
-                       $config['swiftAuthUrl']
-               );
-               // Optional settings
-               $this->authTTL = isset( $config['swiftAuthTTL'] )
-                       ? $config['swiftAuthTTL']
-                       : 5 * 60; // some sane number
-               $this->swiftAnonUser = isset( $config['swiftAnonUser'] )
-                       ? $config['swiftAnonUser']
-                       : '';
-               $this->shardViaHashLevels = isset( $config['shardViaHashLevels'] )
-                       ? $config['shardViaHashLevels']
-                       : '';
-               $this->swiftUseCDN = isset( $config['swiftUseCDN'] )
-                       ? $config['swiftUseCDN']
-                       : false;
-               $this->swiftCDNExpiry = isset( $config['swiftCDNExpiry'] )
-                       ? $config['swiftCDNExpiry']
-                       : 3600; // hour
-               $this->swiftCDNPurgable = isset( $config['swiftCDNPurgable'] )
-                       ? $config['swiftCDNPurgable']
-                       : true;
-               // Cache container info to mask latency
-               $this->memCache = wfGetMainCache();
-       }
-
-       /**
-        * @see FileBackendStore::resolveContainerPath()
-        * @return null
-        */
-       protected function resolveContainerPath( $container, $relStoragePath ) {
-               if ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
-                       return null; // too long for Swift
-               }
-               return $relStoragePath;
-       }
-
-       /**
-        * @see FileBackendStore::isPathUsableInternal()
-        * @return bool
-        */
-       public function isPathUsableInternal( $storagePath ) {
-               list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath );
-               if ( $rel === null ) {
-                       return false; // invalid
-               }
-
-               try {
-                       $this->getContainer( $container );
-                       return true; // container exists
-               } catch ( NoSuchContainerException $e ) {
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, null, __METHOD__, array( 'path' => $storagePath ) );
-               }
-
-               return false;
-       }
-
-       /**
-        * @see FileBackendStore::doCreateInternal()
-        * @return Status
-        */
-       protected function doCreateInternal( array $params ) {
-               $status = Status::newGood();
-
-               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
-               if ( $dstRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               // (a) Check the destination container and object
-               try {
-                       $dContObj = $this->getContainer( $dstCont );
-                       if ( empty( $params['overwrite'] ) &&
-                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
-                       {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               } catch ( NoSuchContainerException $e ) {
-                       $status->fatal( 'backend-fail-create', $params['dst'] );
-                       return $status;
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               // (b) Get a SHA-1 hash of the object
-               $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 );
-
-               // (c) Actually create the object
-               try {
-                       // Create a fresh CF_Object with no fields preloaded.
-                       // We don't want to preserve headers, metadata, and such.
-                       $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
-                       // Note: metadata keys stored as [Upper case char][[Lower case char]...]
-                       $obj->metadata = array( 'Sha1base36' => $sha1Hash );
-                       // Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59).
-                       // The MD5 here will be checked within Swift against its own MD5.
-                       $obj->set_etag( md5( $params['content'] ) );
-                       // Use the same content type as StreamFile for security
-                       $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
-                       if ( !strlen( $obj->content_type ) ) { // special case
-                               $obj->content_type = 'unknown/unknown';
-                       }
-                       if ( !empty( $params['async'] ) ) { // deferred
-                               $handle = $obj->write_async( $params['content'] );
-                               $status->value = new SwiftFileOpHandle( $this, $params, 'Create', $handle );
-                               $status->value->affectedObjects[] = $obj;
-                       } else { // actually write the object in Swift
-                               $obj->write( $params['content'] );
-                               $this->purgeCDNCache( array( $obj ) );
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( BadContentTypeException $e ) {
-                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseCreate( CF_Async_Op $cfOp, Status $status, array $params ) {
-               try {
-                       $cfOp->getLastResponse();
-               } catch ( BadContentTypeException $e ) {
-                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doStoreInternal()
-        * @return Status
-        */
-       protected function doStoreInternal( array $params ) {
-               $status = Status::newGood();
-
-               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
-               if ( $dstRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               // (a) Check the destination container and object
-               try {
-                       $dContObj = $this->getContainer( $dstCont );
-                       if ( empty( $params['overwrite'] ) &&
-                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
-                       {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               } catch ( NoSuchContainerException $e ) {
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-                       return $status;
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               // (b) Get a SHA-1 hash of the object
-               $sha1Hash = sha1_file( $params['src'] );
-               if ( $sha1Hash === false ) { // source doesn't exist?
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-                       return $status;
-               }
-               $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 );
-
-               // (c) Actually store the object
-               try {
-                       // Create a fresh CF_Object with no fields preloaded.
-                       // We don't want to preserve headers, metadata, and such.
-                       $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
-                       // Note: metadata keys stored as [Upper case char][[Lower case char]...]
-                       $obj->metadata = array( 'Sha1base36' => $sha1Hash );
-                       // The MD5 here will be checked within Swift against its own MD5.
-                       $obj->set_etag( md5_file( $params['src'] ) );
-                       // Use the same content type as StreamFile for security
-                       $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
-                       if ( !strlen( $obj->content_type ) ) { // special case
-                               $obj->content_type = 'unknown/unknown';
-                       }
-                       if ( !empty( $params['async'] ) ) { // deferred
-                               wfSuppressWarnings();
-                               $fp = fopen( $params['src'], 'rb' );
-                               wfRestoreWarnings();
-                               if ( !$fp ) {
-                                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-                               } else {
-                                       $handle = $obj->write_async( $fp, filesize( $params['src'] ), true );
-                                       $status->value = new SwiftFileOpHandle( $this, $params, 'Store', $handle );
-                                       $status->value->resourcesToClose[] = $fp;
-                                       $status->value->affectedObjects[] = $obj;
-                               }
-                       } else { // actually write the object in Swift
-                               $obj->load_from_filename( $params['src'], true ); // calls $obj->write()
-                               $this->purgeCDNCache( array( $obj ) );
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( BadContentTypeException $e ) {
-                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
-               } catch ( IOException $e ) {
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseStore( CF_Async_Op $cfOp, Status $status, array $params ) {
-               try {
-                       $cfOp->getLastResponse();
-               } catch ( BadContentTypeException $e ) {
-                       $status->fatal( 'backend-fail-contenttype', $params['dst'] );
-               } catch ( IOException $e ) {
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doCopyInternal()
-        * @return Status
-        */
-       protected function doCopyInternal( array $params ) {
-               $status = Status::newGood();
-
-               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
-               if ( $srcRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
-                       return $status;
-               }
-
-               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
-               if ( $dstRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               // (a) Check the source/destination containers and destination object
-               try {
-                       $sContObj = $this->getContainer( $srcCont );
-                       $dContObj = $this->getContainer( $dstCont );
-                       if ( empty( $params['overwrite'] ) &&
-                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
-                       {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               } catch ( NoSuchContainerException $e ) {
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-                       return $status;
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               // (b) Actually copy the file to the destination
-               try {
-                       $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
-                       if ( !empty( $params['async'] ) ) { // deferred
-                               $handle = $sContObj->copy_object_to_async( $srcRel, $dContObj, $dstRel );
-                               $status->value = new SwiftFileOpHandle( $this, $params, 'Copy', $handle );
-                               $status->value->affectedObjects[] = $dstObj;
-                       } else { // actually write the object in Swift
-                               $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel );
-                               $this->purgeCDNCache( array( $dstObj ) );
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( NoSuchObjectException $e ) { // source object does not exist
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseCopy( CF_Async_Op $cfOp, Status $status, array $params ) {
-               try {
-                       $cfOp->getLastResponse();
-               } catch ( NoSuchObjectException $e ) { // source object does not exist
-                       $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doMoveInternal()
-        * @return Status
-        */
-       protected function doMoveInternal( array $params ) {
-               $status = Status::newGood();
-
-               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
-               if ( $srcRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
-                       return $status;
-               }
-
-               list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
-               if ( $dstRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
-                       return $status;
-               }
-
-               // (a) Check the source/destination containers and destination object
-               try {
-                       $sContObj = $this->getContainer( $srcCont );
-                       $dContObj = $this->getContainer( $dstCont );
-                       if ( empty( $params['overwrite'] ) &&
-                               $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
-                       {
-                               $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
-                               return $status;
-                       }
-               } catch ( NoSuchContainerException $e ) {
-                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
-                       return $status;
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               // (b) Actually move the file to the destination
-               try {
-                       $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
-                       $dstObj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
-                       if ( !empty( $params['async'] ) ) { // deferred
-                               $handle = $sContObj->move_object_to_async( $srcRel, $dContObj, $dstRel );
-                               $status->value = new SwiftFileOpHandle( $this, $params, 'Move', $handle );
-                               $status->value->affectedObjects[] = $srcObj;
-                               $status->value->affectedObjects[] = $dstObj;
-                       } else { // actually write the object in Swift
-                               $sContObj->move_object_to( $srcRel, $dContObj, $dstRel );
-                               $this->purgeCDNCache( array( $srcObj, $dstObj ) );
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( NoSuchObjectException $e ) { // source object does not exist
-                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseMove( CF_Async_Op $cfOp, Status $status, array $params ) {
-               try {
-                       $cfOp->getLastResponse();
-               } catch ( NoSuchObjectException $e ) { // source object does not exist
-                       $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doDeleteInternal()
-        * @return Status
-        */
-       protected function doDeleteInternal( array $params ) {
-               $status = Status::newGood();
-
-               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
-               if ( $srcRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
-                       return $status;
-               }
-
-               try {
-                       $sContObj = $this->getContainer( $srcCont );
-                       $srcObj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
-                       if ( !empty( $params['async'] ) ) { // deferred
-                               $handle = $sContObj->delete_object_async( $srcRel );
-                               $status->value = new SwiftFileOpHandle( $this, $params, 'Delete', $handle );
-                               $status->value->affectedObjects[] = $srcObj;
-                       } else { // actually write the object in Swift
-                               $sContObj->delete_object( $srcRel );
-                               $this->purgeCDNCache( array( $srcObj ) );
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( NoSuchContainerException $e ) {
-                       $status->fatal( 'backend-fail-delete', $params['src'] );
-               } catch ( NoSuchObjectException $e ) {
-                       if ( empty( $params['ignoreMissingSource'] ) ) {
-                               $status->fatal( 'backend-fail-delete', $params['src'] );
-                       }
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see SwiftFileBackend::doExecuteOpHandlesInternal()
-        */
-       protected function _getResponseDelete( CF_Async_Op $cfOp, Status $status, array $params ) {
-               try {
-                       $cfOp->getLastResponse();
-               } catch ( NoSuchContainerException $e ) {
-                       $status->fatal( 'backend-fail-delete', $params['src'] );
-               } catch ( NoSuchObjectException $e ) {
-                       if ( empty( $params['ignoreMissingSource'] ) ) {
-                               $status->fatal( 'backend-fail-delete', $params['src'] );
-                       }
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doPrepareInternal()
-        * @return Status
-        */
-       protected function doPrepareInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
-
-               // (a) Check if container already exists
-               try {
-                       $contObj = $this->getContainer( $fullCont );
-                       // NoSuchContainerException not thrown: container must exist
-                       return $status; // already exists
-               } catch ( NoSuchContainerException $e ) {
-                       // NoSuchContainerException thrown: container does not exist
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               // (b) Create container as needed
-               try {
-                       $contObj = $this->createContainer( $fullCont );
-                       if ( !empty( $params['noAccess'] ) ) {
-                               // Make container private to end-users...
-                               $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
-                       } else {
-                               // Make container public to end-users...
-                               $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
-                       }
-                       if ( $this->swiftUseCDN ) { // Rackspace style CDN
-                               $contObj->make_public( $this->swiftCDNExpiry );
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doSecureInternal()
-        * @return Status
-        */
-       protected function doSecureInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
-               if ( empty( $params['noAccess'] ) ) {
-                       return $status; // nothing to do
-               }
-
-               // Restrict container from end-users...
-               try {
-                       // doPrepareInternal() should have been called,
-                       // so the Swift container should already exist...
-                       $contObj = $this->getContainer( $fullCont ); // normally a cache hit
-                       // NoSuchContainerException not thrown: container must exist
-
-                       // Make container private to end-users...
-                       $status->merge( $this->setContainerAccess(
-                               $contObj,
-                               array( $this->auth->username ), // read
-                               array( $this->auth->username ) // write
-                       ) );
-                       if ( $this->swiftUseCDN && $contObj->is_public() ) { // Rackspace style CDN
-                               $contObj->make_private();
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doPublishInternal()
-        * @return Status
-        */
-       protected function doPublishInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
-
-               // Unrestrict container from end-users...
-               try {
-                       // doPrepareInternal() should have been called,
-                       // so the Swift container should already exist...
-                       $contObj = $this->getContainer( $fullCont ); // normally a cache hit
-                       // NoSuchContainerException not thrown: container must exist
-
-                       // Make container public to end-users...
-                       if ( $this->swiftAnonUser != '' ) {
-                               $status->merge( $this->setContainerAccess(
-                                       $contObj,
-                                       array( $this->auth->username, $this->swiftAnonUser ), // read
-                                       array( $this->auth->username, $this->swiftAnonUser ) // write
-                               ) );
-                       } else {
-                               $status->merge( $this->setContainerAccess(
-                                       $contObj,
-                                       array( $this->auth->username, '.r:*' ), // read
-                                       array( $this->auth->username ) // write
-                               ) );
-                       }
-                       if ( $this->swiftUseCDN && !$contObj->is_public() ) { // Rackspace style CDN
-                               $contObj->make_public();
-                       }
-               } catch ( CDNNotEnabledException $e ) {
-                       // CDN not enabled; nothing to see here
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doCleanInternal()
-        * @return Status
-        */
-       protected function doCleanInternal( $fullCont, $dir, array $params ) {
-               $status = Status::newGood();
-
-               // Only containers themselves can be removed, all else is virtual
-               if ( $dir != '' ) {
-                       return $status; // nothing to do
-               }
-
-               // (a) Check the container
-               try {
-                       $contObj = $this->getContainer( $fullCont, true );
-               } catch ( NoSuchContainerException $e ) {
-                       return $status; // ok, nothing to do
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               // (b) Delete the container if empty
-               if ( $contObj->object_count == 0 ) {
-                       try {
-                               $this->deleteContainer( $fullCont );
-                       } catch ( NoSuchContainerException $e ) {
-                               return $status; // race?
-                       } catch ( NonEmptyContainerException $e ) {
-                               return $status; // race? consistency delay?
-                       } catch ( CloudFilesException $e ) { // some other exception?
-                               $this->handleException( $e, $status, __METHOD__, $params );
-                               return $status;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::doFileExists()
-        * @return array|bool|null
-        */
-       protected function doGetFileStat( array $params ) {
-               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
-               if ( $srcRel === null ) {
-                       return false; // invalid storage path
-               }
-
-               $stat = false;
-               try {
-                       $contObj = $this->getContainer( $srcCont );
-                       $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) );
-                       $this->addMissingMetadata( $srcObj, $params['src'] );
-                       $stat = array(
-                               // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
-                               'mtime' => wfTimestamp( TS_MW, $srcObj->last_modified ),
-                               'size'  => (int)$srcObj->content_length,
-                               'sha1'  => $srcObj->metadata['Sha1base36']
-                       );
-               } catch ( NoSuchContainerException $e ) {
-               } catch ( NoSuchObjectException $e ) {
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $stat = null;
-                       $this->handleException( $e, null, __METHOD__, $params );
-               }
-
-               return $stat;
-       }
-
-       /**
-        * Fill in any missing object metadata and save it to Swift
-        *
-        * @param $obj CF_Object
-        * @param $path string Storage path to object
-        * @return bool Success
-        * @throws Exception cloudfiles exceptions
-        */
-       protected function addMissingMetadata( CF_Object $obj, $path ) {
-               if ( isset( $obj->metadata['Sha1base36'] ) ) {
-                       return true; // nothing to do
-               }
-               $status = Status::newGood();
-               $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status );
-               if ( $status->isOK() ) {
-                       # Do not stat the file in getLocalCopy() to avoid infinite loops
-                       $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1, 'nostat' => 1 ) );
-                       if ( $tmpFile ) {
-                               $hash = $tmpFile->getSha1Base36();
-                               if ( $hash !== false ) {
-                                       $obj->metadata['Sha1base36'] = $hash;
-                                       $obj->sync_metadata(); // save to Swift
-                                       return true; // success
-                               }
-                       }
-               }
-               $obj->metadata['Sha1base36'] = false;
-               return false; // failed
-       }
-
-       /**
-        * @see FileBackend::getFileContents()
-        * @return bool|null|string
-        */
-       public function getFileContents( array $params ) {
-               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
-               if ( $srcRel === null ) {
-                       return false; // invalid storage path
-               }
-
-               if ( !$this->fileExists( $params ) ) {
-                       return null;
-               }
-
-               $data = false;
-               try {
-                       $sContObj = $this->getContainer( $srcCont );
-                       $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
-                       $data = $obj->read( $this->headersFromParams( $params ) );
-               } catch ( NoSuchContainerException $e ) {
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, null, __METHOD__, $params );
-               }
-
-               return $data;
-       }
-
-       /**
-        * @see FileBackendStore::doDirectoryExists()
-        * @return bool|null
-        */
-       protected function doDirectoryExists( $fullCont, $dir, array $params ) {
-               try {
-                       $container = $this->getContainer( $fullCont );
-                       $prefix = ( $dir == '' ) ? null : "{$dir}/";
-                       return ( count( $container->list_objects( 1, null, $prefix ) ) > 0 );
-               } catch ( NoSuchContainerException $e ) {
-                       return false;
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, null, __METHOD__,
-                               array( 'cont' => $fullCont, 'dir' => $dir ) );
-               }
-
-               return null; // error
-       }
-
-       /**
-        * @see FileBackendStore::getDirectoryListInternal()
-        * @return SwiftFileBackendDirList
-        */
-       public function getDirectoryListInternal( $fullCont, $dir, array $params ) {
-               return new SwiftFileBackendDirList( $this, $fullCont, $dir, $params );
-       }
-
-       /**
-        * @see FileBackendStore::getFileListInternal()
-        * @return SwiftFileBackendFileList
-        */
-       public function getFileListInternal( $fullCont, $dir, array $params ) {
-               return new SwiftFileBackendFileList( $this, $fullCont, $dir, $params );
-       }
-
-       /**
-        * Do not call this function outside of SwiftFileBackendFileList
-        *
-        * @param $fullCont string Resolved container name
-        * @param $dir string Resolved storage directory with no trailing slash
-        * @param $after string|null Storage path of file to list items after
-        * @param $limit integer Max number of items to list
-        * @param $params Array Includes flag for 'topOnly'
-        * @return Array List of relative paths of dirs directly under $dir
-        */
-       public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
-               $dirs = array();
-               if ( $after === INF ) {
-                       return $dirs; // nothing more
-               }
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-
-               try {
-                       $container = $this->getContainer( $fullCont );
-                       $prefix = ( $dir == '' ) ? null : "{$dir}/";
-                       // Non-recursive: only list dirs right under $dir
-                       if ( !empty( $params['topOnly'] ) ) {
-                               $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
-                               foreach ( $objects as $object ) { // files and dirs
-                                       if ( substr( $object, -1 ) === '/' ) {
-                                               $dirs[] = $object; // directories end in '/'
-                                       }
-                               }
-                       // Recursive: list all dirs under $dir and its subdirs
-                       } else {
-                               // Get directory from last item of prior page
-                               $lastDir = $this->getParentDir( $after ); // must be first page
-                               $objects = $container->list_objects( $limit, $after, $prefix );
-                               foreach ( $objects as $object ) { // files
-                                       $objectDir = $this->getParentDir( $object ); // directory of object
-                                       if ( $objectDir !== false ) { // file has a parent dir
-                                               // Swift stores paths in UTF-8, using binary sorting.
-                                               // See function "create_container_table" in common/db.py.
-                                               // If a directory is not "greater" than the last one,
-                                               // then it was already listed by the calling iterator.
-                                               if ( $objectDir > $lastDir ) {
-                                                       $pDir = $objectDir;
-                                                       do { // add dir and all its parent dirs
-                                                               $dirs[] = "{$pDir}/";
-                                                               $pDir = $this->getParentDir( $pDir );
-                                                       } while ( $pDir !== false // sanity
-                                                               && $pDir > $lastDir // not done already
-                                                               && strlen( $pDir ) > strlen( $dir ) // within $dir
-                                                       );
-                                               }
-                                               $lastDir = $objectDir;
-                                       }
-                               }
-                       }
-                       if ( count( $objects ) < $limit ) {
-                               $after = INF; // avoid a second RTT
-                       } else {
-                               $after = end( $objects ); // update last item
-                       }
-               } catch ( NoSuchContainerException $e ) {
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, null, __METHOD__,
-                               array( 'cont' => $fullCont, 'dir' => $dir ) );
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               return $dirs;
-       }
-
-       protected function getParentDir( $path ) {
-               return ( strpos( $path, '/' ) !== false ) ? dirname( $path ) : false;
-       }
-
-       /**
-        * Do not call this function outside of SwiftFileBackendFileList
-        *
-        * @param $fullCont string Resolved container name
-        * @param $dir string Resolved storage directory with no trailing slash
-        * @param $after string|null Storage path of file to list items after
-        * @param $limit integer Max number of items to list
-        * @param $params Array Includes flag for 'topOnly'
-        * @return Array List of relative paths of files under $dir
-        */
-       public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
-               $files = array();
-               if ( $after === INF ) {
-                       return $files; // nothing more
-               }
-               wfProfileIn( __METHOD__ . '-' . $this->name );
-
-               try {
-                       $container = $this->getContainer( $fullCont );
-                       $prefix = ( $dir == '' ) ? null : "{$dir}/";
-                       // Non-recursive: only list files right under $dir
-                       if ( !empty( $params['topOnly'] ) ) { // files and dirs
-                               $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
-                               foreach ( $objects as $object ) {
-                                       if ( substr( $object, -1 ) !== '/' ) {
-                                               $files[] = $object; // directories end in '/'
-                                       }
-                               }
-                       // Recursive: list all files under $dir and its subdirs
-                       } else { // files
-                               $objects = $container->list_objects( $limit, $after, $prefix );
-                               $files = $objects;
-                       }
-                       if ( count( $objects ) < $limit ) {
-                               $after = INF; // avoid a second RTT
-                       } else {
-                               $after = end( $objects ); // update last item
-                       }
-               } catch ( NoSuchContainerException $e ) {
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, null, __METHOD__,
-                               array( 'cont' => $fullCont, 'dir' => $dir ) );
-               }
-
-               wfProfileOut( __METHOD__ . '-' . $this->name );
-               return $files;
-       }
-
-       /**
-        * @see FileBackendStore::doGetFileSha1base36()
-        * @return bool
-        */
-       protected function doGetFileSha1base36( array $params ) {
-               $stat = $this->getFileStat( $params );
-               if ( $stat ) {
-                       return $stat['sha1'];
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @see FileBackendStore::doStreamFile()
-        * @return Status
-        */
-       protected function doStreamFile( array $params ) {
-               $status = Status::newGood();
-
-               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
-               if ( $srcRel === null ) {
-                       $status->fatal( 'backend-fail-invalidpath', $params['src'] );
-               }
-
-               try {
-                       $cont = $this->getContainer( $srcCont );
-               } catch ( NoSuchContainerException $e ) {
-                       $status->fatal( 'backend-fail-stream', $params['src'] );
-                       return $status;
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-                       return $status;
-               }
-
-               try {
-                       $output = fopen( 'php://output', 'wb' );
-                       $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD
-                       $obj->stream( $output, $this->headersFromParams( $params ) );
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, $status, __METHOD__, $params );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FileBackendStore::getLocalCopy()
-        * @return null|TempFSFile
-        */
-       public function getLocalCopy( array $params ) {
-               list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
-               if ( $srcRel === null ) {
-                       return null;
-               }
-
-               # Check the recursion guard to avoid loops when filling metadata
-               if ( empty( $params['nostat'] ) && !$this->fileExists( $params ) ) {
-                       return null;
-               }
-
-               $tmpFile = null;
-               try {
-                       $sContObj = $this->getContainer( $srcCont );
-                       $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
-                       // Get source file extension
-                       $ext = FileBackend::extensionFromPath( $srcRel );
-                       // Create a new temporary file...
-                       $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext );
-                       if ( $tmpFile ) {
-                               $handle = fopen( $tmpFile->getPath(), 'wb' );
-                               if ( $handle ) {
-                                       $obj->stream( $handle, $this->headersFromParams( $params ) );
-                                       fclose( $handle );
-                               } else {
-                                       $tmpFile = null; // couldn't open temp file
-                               }
-                       }
-               } catch ( NoSuchContainerException $e ) {
-                       $tmpFile = null;
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $tmpFile = null;
-                       $this->handleException( $e, null, __METHOD__, $params );
-               }
-
-               return $tmpFile;
-       }
-
-       /**
-        * @see FileBackendStore::directoriesAreVirtual()
-        * @return bool
-        */
-       protected function directoriesAreVirtual() {
-               return true;
-       }
-
-       /**
-        * Get headers to send to Swift when reading a file based
-        * on a FileBackend params array, e.g. that of getLocalCopy().
-        * $params is currently only checked for a 'latest' flag.
-        *
-        * @param $params Array
-        * @return Array
-        */
-       protected function headersFromParams( array $params ) {
-               $hdrs = array();
-               if ( !empty( $params['latest'] ) ) {
-                       $hdrs[] = 'X-Newest: true';
-               }
-               return $hdrs;
-       }
-
-       /**
-        * @see FileBackendStore::doExecuteOpHandlesInternal()
-        * @return Array List of corresponding Status objects
-        */
-       protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
-               $statuses = array();
-
-               $cfOps = array(); // list of CF_Async_Op objects
-               foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                       $cfOps[$index] = $fileOpHandle->cfOp;
-               }
-               $batch = new CF_Async_Op_Batch( $cfOps );
-
-               $cfOps = $batch->execute();
-               foreach ( $cfOps as $index => $cfOp ) {
-                       $status = Status::newGood();
-                       try { // catch exceptions; update status
-                               $function = '_getResponse' . $fileOpHandles[$index]->call;
-                               $this->$function( $cfOp, $status, $fileOpHandles[$index]->params );
-                               $this->purgeCDNCache( $fileOpHandles[$index]->affectedObjects );
-                       } catch ( CloudFilesException $e ) { // some other exception?
-                               $this->handleException( $e, $status,
-                                       __CLASS__ . ":$function", $fileOpHandles[$index]->params );
-                       }
-                       $statuses[$index] = $status;
-               }
-
-               return $statuses;
-       }
-
-       /**
-        * Set read/write permissions for a Swift container.
-        *
-        * $readGrps is a list of the possible criteria for a request to have
-        * access to read a container. Each item is one of the following formats:
-        *   - account:user       : Grants access if the request is by the given user
-        *   - .r:<regex>         : Grants access if the request is from a referrer host that
-        *                          matches the expression and the request is not for a listing.
-        *                          Setting this to '*' effectively makes a container public.
-        *   - .rlistings:<regex> : Grants access if the request is from a referrer host that
-        *                          matches the expression and the request for a listing.
-        *
-        * $writeGrps is a list of the possible criteria for a request to have
-        * access to write to a container. Each item is of the following format:
-        *   - account:user       : Grants access if the request is by the given user
-        *
-        * @see http://swift.openstack.org/misc.html#acls
-        *
-        * In general, we don't allow listings to end-users. It's not useful, isn't well-defined
-        * (lists are truncated to 10000 item with no way to page), and is just a performance risk.
-        *
-        * @param $contObj CF_Container Swift container
-        * @param $readGrps Array List of read access routes
-        * @param $writeGrps Array List of write access routes
-        * @return Status
-        */
-       protected function setContainerAccess(
-               CF_Container $contObj, array $readGrps, array $writeGrps
-       ) {
-               $creds = $contObj->cfs_auth->export_credentials();
-
-               $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name );
-
-               // Note: 10 second timeout consistent with php-cloudfiles
-               $req = MWHttpRequest::factory( $url, array( 'method' => 'POST', 'timeout' => 10 ) );
-               $req->setHeader( 'X-Auth-Token', $creds['auth_token'] );
-               $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) );
-               $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) );
-
-               return $req->execute(); // should return 204
-       }
-
-       /**
-        * Purge the CDN cache of affected objects if CDN caching is enabled.
-        * This is for Rackspace/Akamai CDNs.
-        *
-        * @param $objects Array List of CF_Object items
-        * @return void
-        */
-       public function purgeCDNCache( array $objects ) {
-               if ( $this->swiftUseCDN && $this->swiftCDNPurgable ) {
-                       foreach ( $objects as $object ) {
-                               try {
-                                       $object->purge_from_cdn();
-                               } catch ( CDNNotEnabledException $e ) {
-                                       // CDN not enabled; nothing to see here
-                               } catch ( CloudFilesException $e ) {
-                                       $this->handleException( $e, null, __METHOD__,
-                                               array( 'cont' => $object->container->name, 'obj' => $object->name ) );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Get a connection to the Swift proxy
-        *
-        * @return CF_Connection|bool False on failure
-        * @throws CloudFilesException
-        */
-       protected function getConnection() {
-               if ( $this->connException instanceof Exception ) {
-                       throw $this->connException; // failed last attempt
-               }
-               // Session keys expire after a while, so we renew them periodically
-               if ( $this->conn && ( time() - $this->connStarted ) > $this->authTTL ) {
-                       $this->conn->close(); // close active cURL connections
-                       $this->conn = null;
-               }
-               // Authenticate with proxy and get a session key...
-               if ( !$this->conn ) {
-                       $this->connStarted = 0;
-                       $this->connContainers = array();
-                       try {
-                               $this->auth->authenticate();
-                               $this->conn = new CF_Connection( $this->auth );
-                               $this->connStarted = time();
-                       } catch ( CloudFilesException $e ) {
-                               $this->connException = $e; // don't keep re-trying
-                               throw $e; // throw it back
-                       }
-               }
-               return $this->conn;
-       }
-
-       /**
-        * @see FileBackendStore::doClearCache()
-        */
-       protected function doClearCache( array $paths = null ) {
-               $this->connContainers = array(); // clear container object cache
-       }
-
-       /**
-        * Get a Swift container object, possibly from process cache.
-        * Use $reCache if the file count or byte count is needed.
-        *
-        * @param $container string Container name
-        * @param $bypassCache bool Bypass all caches and load from Swift
-        * @return CF_Container
-        * @throws CloudFilesException
-        */
-       protected function getContainer( $container, $bypassCache = false ) {
-               $conn = $this->getConnection(); // Swift proxy connection
-               if ( $bypassCache ) { // purge cache
-                       unset( $this->connContainers[$container] );
-               } elseif ( !isset( $this->connContainers[$container] ) ) {
-                       $this->primeContainerCache( array( $container ) ); // check persistent cache
-               }
-               if ( !isset( $this->connContainers[$container] ) ) {
-                       $contObj = $conn->get_container( $container );
-                       // NoSuchContainerException not thrown: container must exist
-                       if ( count( $this->connContainers ) >= $this->maxContCacheSize ) { // trim cache?
-                               reset( $this->connContainers );
-                               unset( $this->connContainers[key( $this->connContainers )] );
-                       }
-                       $this->connContainers[$container] = $contObj; // cache it
-                       if ( !$bypassCache ) {
-                               $this->setContainerCache( $container, // update persistent cache
-                                       array( 'bytes' => $contObj->bytes_used, 'count' => $contObj->object_count )
-                               );
-                       }
-               }
-               return $this->connContainers[$container];
-       }
-
-       /**
-        * Create a Swift container
-        *
-        * @param $container string Container name
-        * @return CF_Container
-        * @throws CloudFilesException
-        */
-       protected function createContainer( $container ) {
-               $conn = $this->getConnection(); // Swift proxy connection
-               $contObj = $conn->create_container( $container );
-               $this->connContainers[$container] = $contObj; // cache it
-               return $contObj;
-       }
-
-       /**
-        * Delete a Swift container
-        *
-        * @param $container string Container name
-        * @return void
-        * @throws CloudFilesException
-        */
-       protected function deleteContainer( $container ) {
-               $conn = $this->getConnection(); // Swift proxy connection
-               unset( $this->connContainers[$container] ); // purge cache
-               $conn->delete_container( $container );
-       }
-
-       /**
-        * @see FileBackendStore::doPrimeContainerCache()
-        * @return void
-        */
-       protected function doPrimeContainerCache( array $containerInfo ) {
-               try {
-                       $conn = $this->getConnection(); // Swift proxy connection
-                       foreach ( $containerInfo as $container => $info ) {
-                               $this->connContainers[$container] = new CF_Container(
-                                       $conn->cfs_auth,
-                                       $conn->cfs_http,
-                                       $container,
-                                       $info['count'],
-                                       $info['bytes']
-                               );
-                       }
-               } catch ( CloudFilesException $e ) { // some other exception?
-                       $this->handleException( $e, null, __METHOD__, array() );
-               }
-       }
-
-       /**
-        * Log an unexpected exception for this backend.
-        * This also sets the Status object to have a fatal error.
-        *
-        * @param $e Exception
-        * @param $status Status|null
-        * @param $func string
-        * @param $params Array
-        * @return void
-        */
-       protected function handleException( Exception $e, $status, $func, array $params ) {
-               if ( $status instanceof Status ) {
-                       if ( $e instanceof AuthenticationException ) {
-                               $status->fatal( 'backend-fail-connect', $this->name );
-                       } else {
-                               $status->fatal( 'backend-fail-internal', $this->name );
-                       }
-               }
-               if ( $e->getMessage() ) {
-                       trigger_error( "$func: " . $e->getMessage(), E_USER_WARNING );
-               }
-               wfDebugLog( 'SwiftBackend',
-                       get_class( $e ) . " in '{$func}' (given '" . FormatJson::encode( $params ) . "')" .
-                       ( $e->getMessage() ? ": {$e->getMessage()}" : "" )
-               );
-       }
-}
-
-/**
- * @see FileBackendStoreOpHandle
- */
-class SwiftFileOpHandle extends FileBackendStoreOpHandle {
-       /** @var CF_Async_Op */
-       public $cfOp;
-       /** @var Array */
-       public $affectedObjects = array();
-
-       public function __construct( $backend, array $params, $call, CF_Async_Op $cfOp ) {
-               $this->backend = $backend;
-               $this->params = $params;
-               $this->call = $call;
-               $this->cfOp = $cfOp;
-       }
-}
-
-/**
- * SwiftFileBackend helper class to page through listings.
- * Swift also has a listing limit of 10,000 objects for sanity.
- * Do not use this class from places outside SwiftFileBackend.
- *
- * @ingroup FileBackend
- */
-abstract class SwiftFileBackendList implements Iterator {
-       /** @var Array */
-       protected $bufferIter = array();
-       protected $bufferAfter = null; // string; list items *after* this path
-       protected $pos = 0; // integer
-       /** @var Array */
-       protected $params = array();
-
-       /** @var SwiftFileBackend */
-       protected $backend;
-       protected $container; // string; container name
-       protected $dir; // string; storage directory
-       protected $suffixStart; // integer
-
-       const PAGE_SIZE = 9000; // file listing buffer size
-
-       /**
-        * @param $backend SwiftFileBackend
-        * @param $fullCont string Resolved container name
-        * @param $dir string Resolved directory relative to container
-        * @param $params Array
-        */
-       public function __construct( SwiftFileBackend $backend, $fullCont, $dir, array $params ) {
-               $this->backend = $backend;
-               $this->container = $fullCont;
-               $this->dir = $dir;
-               if ( substr( $this->dir, -1 ) === '/' ) {
-                       $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash
-               }
-               if ( $this->dir == '' ) { // whole container
-                       $this->suffixStart = 0;
-               } else { // dir within container
-                       $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
-               }
-               $this->params = $params;
-       }
-
-       /**
-        * @see Iterator::key()
-        * @return integer
-        */
-       public function key() {
-               return $this->pos;
-       }
-
-       /**
-        * @see Iterator::next()
-        * @return void
-        */
-       public function next() {
-               // Advance to the next file in the page
-               next( $this->bufferIter );
-               ++$this->pos;
-               // Check if there are no files left in this page and
-               // advance to the next page if this page was not empty.
-               if ( !$this->valid() && count( $this->bufferIter ) ) {
-                       $this->bufferIter = $this->pageFromList(
-                               $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
-                       ); // updates $this->bufferAfter
-               }
-       }
-
-       /**
-        * @see Iterator::rewind()
-        * @return void
-        */
-       public function rewind() {
-               $this->pos = 0;
-               $this->bufferAfter = null;
-               $this->bufferIter = $this->pageFromList(
-                       $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
-               ); // updates $this->bufferAfter
-       }
-
-       /**
-        * @see Iterator::valid()
-        * @return bool
-        */
-       public function valid() {
-               if ( $this->bufferIter === null ) {
-                       return false; // some failure?
-               } else {
-                       return ( current( $this->bufferIter ) !== false ); // no paths can have this value
-               }
-       }
-
-       /**
-        * Get the given list portion (page)
-        *
-        * @param $container string Resolved container name
-        * @param $dir string Resolved path relative to container
-        * @param $after string|null
-        * @param $limit integer
-        * @param $params Array
-        * @return Traversable|Array|null Returns null on failure
-        */
-       abstract protected function pageFromList( $container, $dir, &$after, $limit, array $params );
-}
-
-/**
- * Iterator for listing directories
- */
-class SwiftFileBackendDirList extends SwiftFileBackendList {
-       /**
-        * @see Iterator::current()
-        * @return string|bool String (relative path) or false
-        */
-       public function current() {
-               return substr( current( $this->bufferIter ), $this->suffixStart, -1 );
-       }
-
-       /**
-        * @see SwiftFileBackendList::pageFromList()
-        * @return Array|null
-        */
-       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
-               return $this->backend->getDirListPageInternal( $container, $dir, $after, $limit, $params );
-       }
-}
-
-/**
- * Iterator for listing regular files
- */
-class SwiftFileBackendFileList extends SwiftFileBackendList {
-       /**
-        * @see Iterator::current()
-        * @return string|bool String (relative path) or false
-        */
-       public function current() {
-               return substr( current( $this->bufferIter ), $this->suffixStart );
-       }
-
-       /**
-        * @see SwiftFileBackendList::pageFromList()
-        * @return Array|null
-        */
-       protected function pageFromList( $container, $dir, &$after, $limit, array $params ) {
-               return $this->backend->getFileListPageInternal( $container, $dir, $after, $limit, $params );
-       }
-}
diff --git a/includes/filerepo/backend/TempFSFile.php b/includes/filerepo/backend/TempFSFile.php
deleted file mode 100644 (file)
index ddc640a..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-<?php
-/**
- * Location holder of files stored temporarily
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- */
-
-/**
- * This class is used to hold the location and do limited manipulation
- * of files stored temporarily (this will be whatever wfTempDir() returns)
- *
- * @ingroup FileBackend
- */
-class TempFSFile extends FSFile {
-       protected $canDelete = false; // bool; garbage collect the temp file
-
-       /** @var Array of active temp files to purge on shutdown */
-       protected static $instances = array();
-
-       /**
-        * Make a new temporary file on the file system.
-        * Temporary files may be purged when the file object falls out of scope.
-        * 
-        * @param $prefix string
-        * @param $extension string
-        * @return TempFSFile|null 
-        */
-       public static function factory( $prefix, $extension = '' ) {
-               $base = wfTempDir() . '/' . $prefix . dechex( mt_rand( 0, 99999999 ) );
-               $ext = ( $extension != '' ) ? ".{$extension}" : "";
-               for ( $attempt = 1; true; $attempt++ ) {
-                       $path = "{$base}-{$attempt}{$ext}";
-                       wfSuppressWarnings();
-                       $newFileHandle = fopen( $path, 'x' );
-                       wfRestoreWarnings();
-                       if ( $newFileHandle ) {
-                               fclose( $newFileHandle );
-                               break; // got it
-                       }
-                       if ( $attempt >= 15 ) {
-                               return null; // give up
-                       }
-               }
-               $tmpFile = new self( $path );
-               $tmpFile->canDelete = true; // safely instantiated
-               return $tmpFile;
-       }
-
-       /**
-        * Purge this file off the file system
-        * 
-        * @return bool Success
-        */
-       public function purge() {
-               $this->canDelete = false; // done
-               wfSuppressWarnings();
-               $ok = unlink( $this->path );
-               wfRestoreWarnings();
-               return $ok;
-       }
-
-       /**
-        * Clean up the temporary file only after an object goes out of scope
-        *
-        * @param $object Object
-        * @return void
-        */
-       public function bind( $object ) {
-               if ( is_object( $object ) ) {
-                       $object->tempFSFileReferences[] = $this;
-               }
-       }
-
-       /**
-        * Set flag to not clean up after the temporary file
-        *
-        * @return void
-        */
-       public function preserve() {
-               $this->canDelete = false;
-       }
-
-       /**
-        * Cleans up after the temporary file by deleting it
-        */
-       function __destruct() {
-               if ( $this->canDelete ) {
-                       wfSuppressWarnings();
-                       unlink( $this->path );
-                       wfRestoreWarnings();
-               }
-       }
-}
diff --git a/includes/filerepo/backend/filejournal/DBFileJournal.php b/includes/filerepo/backend/filejournal/DBFileJournal.php
deleted file mode 100644 (file)
index 0a09894..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-/**
- * Version of FileJournal that logs to a DB table.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileJournal
- * @author Aaron Schulz
- */
-
-/**
- * Version of FileJournal that logs to a DB table
- * @since 1.20
- */
-class DBFileJournal extends FileJournal {
-       protected $wiki = false; // string; wiki DB name
-
-       /**
-        * Construct a new instance from configuration.
-        * $config includes:
-        *     'wiki' : wiki name to use for LoadBalancer
-        * 
-        * @param $config Array
-        */
-       protected function __construct( array $config ) {
-               parent::__construct( $config );
-
-               $this->wiki = $config['wiki'];
-       }
-
-       /**
-        * @see FileJournal::logChangeBatch()
-        * @return Status
-        */
-       protected function doLogChangeBatch( array $entries, $batchId ) {
-               $status = Status::newGood();
-
-               try {
-                       $dbw = $this->getMasterDB();
-               } catch ( DBError $e ) {
-                       $status->fatal( 'filejournal-fail-dbconnect', $this->backend );
-                       return $status;
-               }
-
-               $now = wfTimestamp( TS_UNIX );
-
-               $data = array();
-               foreach ( $entries as $entry ) {
-                       $data[] = array(
-                               'fj_batch_uuid' => $batchId,
-                               'fj_backend'    => $this->backend,
-                               'fj_op'         => $entry['op'],
-                               'fj_path'       => $entry['path'],
-                               'fj_path_sha1'  => wfBaseConvert( sha1( $entry['path'] ), 16, 36, 31 ),
-                               'fj_new_sha1'   => $entry['newSha1'],
-                               'fj_timestamp'  => $dbw->timestamp( $now )
-                       );
-               }
-
-               try {
-                       $dbw->begin();
-                       $dbw->insert( 'filejournal', $data, __METHOD__ );
-                       $dbw->commit();
-               } catch ( DBError $e ) {
-                       $status->fatal( 'filejournal-fail-dbquery', $this->backend );
-                       return $status;
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see FileJournal::doGetChangeEntries()
-        * @return Array
-        * @throws DBError
-        */
-       protected function doGetChangeEntries( $start, $limit ) {
-               $dbw = $this->getMasterDB();
-
-               $res = $dbw->select( 'filejournal', '*',
-                       array(
-                               'fj_backend' => $this->backend,
-                               'fj_id >= ' . $dbw->addQuotes( (int)$start ) ), // $start may be 0
-                       __METHOD__,
-                       array_merge( array( 'ORDER BY' => 'fj_id ASC' ),
-                               $limit ? array( 'LIMIT' => $limit ) : array() )
-               );
-
-               $entries = array();
-               foreach ( $res as $row ) {
-                       $item = array();
-                       foreach ( (array)$row as $key => $value ) {
-                               $item[substr( $key, 3 )] = $value; // "fj_op" => "op"
-                       }
-                       $entries[] = $item;
-               }
-
-               return $entries;
-       }
-
-       /**
-        * @see FileJournal::purgeOldLogs()
-        * @return Status
-        * @throws DBError
-        */
-       protected function doPurgeOldLogs() {
-               $status = Status::newGood();
-               if ( $this->ttlDays <= 0 ) {
-                       return $status; // nothing to do
-               }
-
-               $dbw = $this->getMasterDB();
-               $dbCutoff = $dbw->timestamp( time() - 86400 * $this->ttlDays );
-
-               $dbw->begin();
-               $dbw->delete( 'filejournal',
-                       array( 'fj_timestamp < ' . $dbw->addQuotes( $dbCutoff ) ),
-                       __METHOD__
-               );
-               $dbw->commit();
-
-               return $status;
-       }
-
-       /**
-        * Get a master connection to the logging DB
-        *
-        * @return DatabaseBase
-        * @throws DBError
-        */
-       protected function getMasterDB() {
-               $lb = wfGetLBFactory()->newMainLB();
-               return $lb->getConnection( DB_MASTER, array(), $this->wiki );
-       }
-}
diff --git a/includes/filerepo/backend/filejournal/FileJournal.php b/includes/filerepo/backend/filejournal/FileJournal.php
deleted file mode 100644 (file)
index 234788b..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-<?php
-/**
- * @defgroup FileJournal File journal
- * @ingroup FileBackend
- */
-
-/**
- * File operation journaling.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileJournal
- * @author Aaron Schulz
- */
-
-/**
- * @brief Class for handling file operation journaling.
- *
- * Subclasses should avoid throwing exceptions at all costs.
- *
- * @ingroup FileJournal
- * @since 1.20
- */
-abstract class FileJournal {
-       protected $backend; // string
-       protected $ttlDays; // integer
-
-       /**
-        * Construct a new instance from configuration.
-        * $config includes:
-        *     'ttlDays' : days to keep log entries around (false means "forever")
-        * 
-        * @param $config Array
-        */
-       protected function __construct( array $config ) {
-               $this->ttlDays = isset( $config['ttlDays'] ) ? $config['ttlDays'] : false;
-       }
-
-       /**
-        * Create an appropriate FileJournal object from config
-        *
-        * @param $config Array
-        * @param $backend string A registered file backend name
-        * @throws MWException
-        * @return FileJournal
-        */
-       final public static function factory( array $config, $backend ) {
-               $class = $config['class'];
-               $jrn = new $class( $config );
-               if ( !$jrn instanceof self ) {
-                       throw new MWException( "Class given is not an instance of FileJournal." );
-               }
-               $jrn->backend = $backend;
-               return $jrn;
-       }
-
-       /**
-        * Get a statistically unique ID string
-        * 
-        * @return string <9 char TS_MW timestamp in base 36><22 random base 36 chars>
-        */
-       final public function getTimestampedUUID() {
-               $s = '';
-               for ( $i = 0; $i < 5; $i++ ) {
-                       $s .= mt_rand( 0, 2147483647 );
-               }
-               $s = wfBaseConvert( sha1( $s ), 16, 36, 31 );
-               return substr( wfBaseConvert( wfTimestamp( TS_MW ), 10, 36, 9 ) . $s, 0, 31 );
-       }
-
-       /**
-        * Log changes made by a batch file operation.
-        * $entries is an array of log entries, each of which contains:
-        *     op      : Basic operation name (create, store, copy, delete)
-        *     path    : The storage path of the file
-        *     newSha1 : The final base 36 SHA-1 of the file
-        * Note that 'false' should be used as the SHA-1 for non-existing files.
-        * 
-        * @param $entries Array List of file operations (each an array of parameters)
-        * @param $batchId string UUID string that identifies the operation batch
-        * @return Status
-        */
-       final public function logChangeBatch( array $entries, $batchId ) {
-               if ( !count( $entries ) ) {
-                       return Status::newGood();
-               }
-               return $this->doLogChangeBatch( $entries, $batchId );
-       }
-
-       /**
-        * @see FileJournal::logChangeBatch()
-        * 
-        * @param $entries Array List of file operations (each an array of parameters)
-        * @param $batchId string UUID string that identifies the operation batch
-        * @return Status
-        */
-       abstract protected function doLogChangeBatch( array $entries, $batchId );
-
-       /**
-        * Get an array of file change log entries.
-        * A starting change ID and/or limit can be specified.
-        *
-        * The result as a list of associative arrays, each having:
-        *     id         : unique, monotonic, ID for this change
-        *     batch_uuid : UUID for an operation batch
-        *     backend    : the backend name
-        *     op         : primitive operation (create,update,delete)
-        *     path       : affected storage path
-        *     path_sha1  : base 36 sha1 of the affected storage path
-        *     timestamp  : TS_MW timestamp of the batch change
-
-        * Also, $next is updated to the ID of the next entry.
-        *
-        * @param $start integer Starting change ID or null
-        * @param $limit integer Maximum number of items to return
-        * @param &$next string
-        * @return Array
-        */
-       final public function getChangeEntries( $start = null, $limit = 0, &$next = null ) {
-               $entries = $this->doGetChangeEntries( $start, $limit ? $limit + 1 : 0 );
-               if ( $limit && count( $entries ) > $limit ) {
-                       $last = array_pop( $entries ); // remove the extra entry
-                       $next = $last['id']; // update for next call
-               } else {
-                       $next = null; // end of list
-               }
-               return $entries;
-       }
-
-       /**
-        * @see FileJournal::getChangeEntries()
-        * @return Array
-        */
-       abstract protected function doGetChangeEntries( $start, $limit );
-
-       /**
-        * Purge any old log entries
-        *
-        * @return Status
-        */
-       final public function purgeOldLogs() {
-               return $this->doPurgeOldLogs();
-       }
-
-       /**
-        * @see FileJournal::purgeOldLogs()
-        * @return Status
-        */
-       abstract protected function doPurgeOldLogs();
-}
-
-/**
- * Simple version of FileJournal that does nothing
- * @since 1.20
- */
-class NullFileJournal extends FileJournal {
-       /**
-        * @see FileJournal::logChangeBatch()
-        * @param $entries array
-        * @param $batchId string
-        * @return Status
-        */
-       protected function doLogChangeBatch( array $entries, $batchId ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see FileJournal::doGetChangeEntries()
-        * @return Array
-        */
-       protected function doGetChangeEntries( $start, $limit ) {
-               return array();
-       }
-
-       /**
-        * @see FileJournal::purgeOldLogs()
-        * @return Status
-        */
-       protected function doPurgeOldLogs() {
-               return Status::newGood();
-       }
-}
diff --git a/includes/filerepo/backend/lockmanager/DBLockManager.php b/includes/filerepo/backend/lockmanager/DBLockManager.php
deleted file mode 100644 (file)
index 204ca3b..0000000
+++ /dev/null
@@ -1,374 +0,0 @@
-<?php
-/**
- * Version of LockManager based on using DB table locks.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup LockManager
- */
-
-/**
- * Version of LockManager based on using DB table locks.
- * This is meant for multi-wiki systems that may share files.
- * All locks are blocking, so it might be useful to set a small
- * lock-wait timeout via server config to curtail deadlocks.
- *
- * All lock requests for a resource, identified by a hash string, will map
- * to one bucket. Each bucket maps to one or several peer DBs, each on their
- * own server, all having the filelocks.sql tables (with row-level locking).
- * A majority of peer DBs must agree for a lock to be acquired.
- *
- * Caching is used to avoid hitting servers that are down.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class DBLockManager extends QuorumLockManager {
-       /** @var Array Map of DB names to server config */
-       protected $dbServers; // (DB name => server config array)
-       /** @var BagOStuff */
-       protected $statusCache;
-
-       protected $lockExpiry; // integer number of seconds
-       protected $safeDelay; // integer number of seconds
-
-       protected $session = 0; // random integer
-       /** @var Array Map Database connections (DB name => Database) */
-       protected $conns = array();
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * $config paramaters include:
-        *     'dbServers'   : Associative array of DB names to server configuration.
-        *                     Configuration is an associative array that includes:
-        *                     'host'        - DB server name
-        *                     'dbname'      - DB name
-        *                     'type'        - DB type (mysql,postgres,...)
-        *                     'user'        - DB user
-        *                     'password'    - DB user password
-        *                     'tablePrefix' - DB table prefix
-        *                     'flags'       - DB flags (see DatabaseBase)
-        *     'dbsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
-        *                     each having an odd-numbered list of DB names (peers) as values.
-        *                     Any DB named 'localDBMaster' will automatically use the DB master
-        *                     settings for this wiki (without the need for a dbServers entry).
-        *     'lockExpiry'  : Lock timeout (seconds) for dropped connections. [optional]
-        *                     This tells the DB server how long to wait before assuming
-        *                     connection failure and releasing all the locks for a session.
-        *
-        * @param Array $config
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-
-               $this->dbServers = isset( $config['dbServers'] )
-                       ? $config['dbServers']
-                       : array(); // likely just using 'localDBMaster'
-               // Sanitize srvsByBucket config to prevent PHP errors
-               $this->srvsByBucket = array_filter( $config['dbsByBucket'], 'is_array' );
-               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
-
-               if ( isset( $config['lockExpiry'] ) ) {
-                       $this->lockExpiry = $config['lockExpiry'];
-               } else {
-                       $met = ini_get( 'max_execution_time' );
-                       $this->lockExpiry = $met ? $met : 60; // use some sane amount if 0
-               }
-               $this->safeDelay = ( $this->lockExpiry <= 0 )
-                       ? 60 // pick a safe-ish number to match DB timeout default
-                       : $this->lockExpiry; // cover worst case
-
-               foreach ( $this->srvsByBucket as $bucket ) {
-                       if ( count( $bucket ) > 1 ) { // multiple peers
-                               // Tracks peers that couldn't be queried recently to avoid lengthy
-                               // connection timeouts. This is useless if each bucket has one peer.
-                               try {
-                                       $this->statusCache = ObjectCache::newAccelerator( array() );
-                               } catch ( MWException $e ) {
-                                       trigger_error( __CLASS__ .
-                                               " using multiple DB peers without apc, xcache, or wincache." );
-                               }
-                               break;
-                       }
-               }
-
-               $this->session = wfRandomString( 31 );
-       }
-
-       /**
-        * Get a connection to a lock DB and acquire locks on $paths.
-        * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
-        *
-        * @see QuorumLockManager::getLocksOnServer()
-        * @return Status
-        */
-       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-
-               if ( $type == self::LOCK_EX ) { // writer locks
-                       try {
-                               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
-                               # Build up values for INSERT clause
-                               $data = array();
-                               foreach ( $keys as $key ) {
-                                       $data[] = array( 'fle_key' => $key );
-                               }
-                               # Wait on any existing writers and block new ones if we get in
-                               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
-                               $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
-                       } catch ( DBError $e ) {
-                               foreach ( $paths as $path ) {
-                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                               }
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::freeLocksOnServer()
-        * @return Status
-        */
-       protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
-               return Status::newGood(); // not supported
-       }
-
-       /**
-        * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
-        */
-       protected function releaseAllLocks() {
-               $status = Status::newGood();
-
-               foreach ( $this->conns as $lockDb => $db ) {
-                       if ( $db->trxLevel() ) { // in transaction
-                               try {
-                                       $db->rollback( __METHOD__ ); // finish transaction and kill any rows
-                               } catch ( DBError $e ) {
-                                       $status->fatal( 'lockmanager-fail-db-release', $lockDb );
-                               }
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::isServerUp()
-        * @return bool
-        */
-       protected function isServerUp( $lockSrv ) {
-               if ( !$this->cacheCheckFailures( $lockSrv ) ) {
-                       return false; // recent failure to connect
-               }
-               try {
-                       $this->getConnection( $lockSrv );
-               } catch ( DBError $e ) {
-                       $this->cacheRecordFailure( $lockSrv );
-                       return false; // failed to connect
-               }
-               return true;
-       }
-
-       /**
-        * Get (or reuse) a connection to a lock DB
-        *
-        * @param $lockDb string
-        * @return DatabaseBase
-        * @throws DBError
-        */
-       protected function getConnection( $lockDb ) {
-               if ( !isset( $this->conns[$lockDb] ) ) {
-                       $db = null;
-                       if ( $lockDb === 'localDBMaster' ) {
-                               $lb = wfGetLBFactory()->newMainLB();
-                               $db = $lb->getConnection( DB_MASTER );
-                       } elseif ( isset( $this->dbServers[$lockDb] ) ) {
-                               $config = $this->dbServers[$lockDb];
-                               $db = DatabaseBase::factory( $config['type'], $config );
-                       }
-                       if ( !$db ) {
-                               return null; // config error?
-                       }
-                       $this->conns[$lockDb] = $db;
-                       $this->conns[$lockDb]->clearFlag( DBO_TRX );
-                       # If the connection drops, try to avoid letting the DB rollback
-                       # and release the locks before the file operations are finished.
-                       # This won't handle the case of DB server restarts however.
-                       $options = array();
-                       if ( $this->lockExpiry > 0 ) {
-                               $options['connTimeout'] = $this->lockExpiry;
-                       }
-                       $this->conns[$lockDb]->setSessionOptions( $options );
-                       $this->initConnection( $lockDb, $this->conns[$lockDb] );
-               }
-               if ( !$this->conns[$lockDb]->trxLevel() ) {
-                       $this->conns[$lockDb]->begin( __METHOD__ ); // start transaction
-               }
-               return $this->conns[$lockDb];
-       }
-
-       /**
-        * Do additional initialization for new lock DB connection
-        *
-        * @param $lockDb string
-        * @param $db DatabaseBase
-        * @return void
-        * @throws DBError
-        */
-       protected function initConnection( $lockDb, DatabaseBase $db ) {}
-
-       /**
-        * Checks if the DB has not recently had connection/query errors.
-        * This just avoids wasting time on doomed connection attempts.
-        *
-        * @param $lockDb string
-        * @return bool
-        */
-       protected function cacheCheckFailures( $lockDb ) {
-               return ( $this->statusCache && $this->safeDelay > 0 )
-                       ? !$this->statusCache->get( $this->getMissKey( $lockDb ) )
-                       : true;
-       }
-
-       /**
-        * Log a lock request failure to the cache
-        *
-        * @param $lockDb string
-        * @return bool Success
-        */
-       protected function cacheRecordFailure( $lockDb ) {
-               return ( $this->statusCache && $this->safeDelay > 0 )
-                       ? $this->statusCache->set( $this->getMissKey( $lockDb ), 1, $this->safeDelay )
-                       : true;
-       }
-
-       /**
-        * Get a cache key for recent query misses for a DB
-        *
-        * @param $lockDb string
-        * @return string
-        */
-       protected function getMissKey( $lockDb ) {
-               $lockDb = ( $lockDb === 'localDBMaster' ) ? wfWikiID() : $lockDb; // non-relative
-               return 'dblockmanager:downservers:' . str_replace( ' ', '_', $lockDb );
-       }
-
-       /**
-        * Make sure remaining locks get cleared for sanity
-        */
-       function __destruct() {
-               foreach ( $this->conns as $db ) {
-                       if ( $db->trxLevel() ) { // in transaction
-                               try {
-                                       $db->rollback( __METHOD__ ); // finish transaction and kill any rows
-                               } catch ( DBError $e ) {
-                                       // oh well
-                               }
-                       }
-                       $db->close();
-               }
-       }
-}
-
-/**
- * MySQL version of DBLockManager that supports shared locks.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * @ingroup LockManager
- */
-class MySqlLockManager extends DBLockManager {
-       /** @var Array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = array(
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       );
-
-       /**
-        * @param $lockDb string
-        * @param $db DatabaseBase
-        */
-       protected function initConnection( $lockDb, DatabaseBase $db ) {
-               # Let this transaction see lock rows from other transactions
-               $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
-       }
-
-       /**
-        * Get a connection to a lock DB and acquire locks on $paths.
-        * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
-        *
-        * @see DBLockManager::getLocksOnServer()
-        * @return Status
-        */
-       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-
-               $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
-               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
-               # Build up values for INSERT clause
-               $data = array();
-               foreach ( $keys as $key ) {
-                       $data[] = array( 'fls_key' => $key, 'fls_session' => $this->session );
-               }
-               # Block new writers...
-               $db->insert( 'filelocks_shared', $data, __METHOD__, array( 'IGNORE' ) );
-               # Actually do the locking queries...
-               if ( $type == self::LOCK_SH ) { // reader locks
-                       # Bail if there are any existing writers...
-                       $blocked = $db->selectField( 'filelocks_exclusive', '1',
-                               array( 'fle_key' => $keys ),
-                               __METHOD__
-                       );
-                       # Prospective writers that haven't yet updated filelocks_exclusive
-                       # will recheck filelocks_shared after doing so and bail due to our entry.
-               } else { // writer locks
-                       $encSession = $db->addQuotes( $this->session );
-                       # Bail if there are any existing writers...
-                       # The may detect readers, but the safe check for them is below.
-                       # Note: if two writers come at the same time, both bail :)
-                       $blocked = $db->selectField( 'filelocks_shared', '1',
-                               array( 'fls_key' => $keys, "fls_session != $encSession" ),
-                               __METHOD__
-                       );
-                       if ( !$blocked ) {
-                               # Build up values for INSERT clause
-                               $data = array();
-                               foreach ( $keys as $key ) {
-                                       $data[] = array( 'fle_key' => $key );
-                               }
-                               # Block new readers/writers...
-                               $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
-                               # Bail if there are any existing readers...
-                               $blocked = $db->selectField( 'filelocks_shared', '1',
-                                       array( 'fls_key' => $keys, "fls_session != $encSession" ),
-                                       __METHOD__
-                               );
-                       }
-               }
-
-               if ( $blocked ) {
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                       }
-               }
-
-               return $status;
-       }
-}
diff --git a/includes/filerepo/backend/lockmanager/FSLockManager.php b/includes/filerepo/backend/lockmanager/FSLockManager.php
deleted file mode 100644 (file)
index 53f3e9f..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-<?php
-/**
- * Simple version of LockManager based on using FS lock files.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup LockManager
- */
-
-/**
- * Simple version of LockManager based on using FS lock files.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * This should work fine for small sites running off one server.
- * Do not use this with 'lockDirectory' set to an NFS mount unless the
- * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
- * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class FSLockManager extends LockManager {
-       /** @var Array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = array(
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       );
-
-       protected $lockDir; // global dir for all servers
-
-       /** @var Array Map of (locked key => lock type => lock file handle) */
-       protected $handles = array();
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * $config includes:
-        *     'lockDirectory' : Directory containing the lock files
-        *
-        * @param array $config
-        */
-       function __construct( array $config ) {
-               parent::__construct( $config );
-
-               $this->lockDir = $config['lockDirectory'];
-       }
-
-       /**
-        * @see LockManager::doLock()
-        * @param $paths array
-        * @param $type int
-        * @return Status
-        */
-       protected function doLock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               $lockedPaths = array(); // files locked in this attempt
-               foreach ( $paths as $path ) {
-                       $status->merge( $this->doSingleLock( $path, $type ) );
-                       if ( $status->isOK() ) {
-                               $lockedPaths[] = $path;
-                       } else {
-                               // Abort and unlock everything
-                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
-                               return $status;
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see LockManager::doUnlock()
-        * @param $paths array
-        * @param $type int
-        * @return Status
-        */
-       protected function doUnlock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               foreach ( $paths as $path ) {
-                       $status->merge( $this->doSingleUnlock( $path, $type ) );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Lock a single resource key
-        *
-        * @param $path string
-        * @param $type integer
-        * @return Status
-        */
-       protected function doSingleLock( $path, $type ) {
-               $status = Status::newGood();
-
-               if ( isset( $this->locksHeld[$path][$type] ) ) {
-                       ++$this->locksHeld[$path][$type];
-               } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
-                       $this->locksHeld[$path][$type] = 1;
-               } else {
-                       wfSuppressWarnings();
-                       $handle = fopen( $this->getLockPath( $path ), 'a+' );
-                       wfRestoreWarnings();
-                       if ( !$handle ) { // lock dir missing?
-                               wfMkdirParents( $this->lockDir );
-                               $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
-                       }
-                       if ( $handle ) {
-                               // Either a shared or exclusive lock
-                               $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
-                               if ( flock( $handle, $lock | LOCK_NB ) ) {
-                                       // Record this lock as active
-                                       $this->locksHeld[$path][$type] = 1;
-                                       $this->handles[$path][$type] = $handle;
-                               } else {
-                                       fclose( $handle );
-                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                               }
-                       } else {
-                               $status->fatal( 'lockmanager-fail-openlock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * Unlock a single resource key
-        *
-        * @param $path string
-        * @param $type integer
-        * @return Status
-        */
-       protected function doSingleUnlock( $path, $type ) {
-               $status = Status::newGood();
-
-               if ( !isset( $this->locksHeld[$path] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $path );
-               } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
-                       $status->warning( 'lockmanager-notlocked', $path );
-               } else {
-                       $handlesToClose = array();
-                       --$this->locksHeld[$path][$type];
-                       if ( $this->locksHeld[$path][$type] <= 0 ) {
-                               unset( $this->locksHeld[$path][$type] );
-                               // If a LOCK_SH comes in while we have a LOCK_EX, we don't
-                               // actually add a handler, so check for handler existence.
-                               if ( isset( $this->handles[$path][$type] ) ) {
-                                       if ( $type === self::LOCK_EX
-                                               && isset( $this->locksHeld[$path][self::LOCK_SH] )
-                                               && !isset( $this->handles[$path][self::LOCK_SH] ) )
-                                       {
-                                               // EX lock came first: move this handle to the SH one
-                                               $this->handles[$path][self::LOCK_SH] = $this->handles[$path][$type];
-                                       } else {
-                                               // Mark this handle to be unlocked and closed
-                                               $handlesToClose[] = $this->handles[$path][$type];
-                                       }
-                                       unset( $this->handles[$path][$type] );
-                               }
-                       }
-                       if ( !count( $this->locksHeld[$path] ) ) {
-                               unset( $this->locksHeld[$path] ); // no locks on this path
-                       }
-                       // Unlock handles to release locks and delete
-                       // any lock files that end up with no locks on them...
-                       if ( wfIsWindows() ) {
-                               // Windows: for any process, including this one,
-                               // calling unlink() on a locked file will fail
-                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
-                               $status->merge( $this->pruneKeyLockFiles( $path ) );
-                       } else {
-                               // Unix: unlink() can be used on files currently open by this
-                               // process and we must do so in order to avoid race conditions
-                               $status->merge( $this->pruneKeyLockFiles( $path ) );
-                               $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @param $path string
-        * @param $handlesToClose array
-        * @return Status
-        */
-       private function closeLockHandles( $path, array $handlesToClose ) {
-               $status = Status::newGood();
-               foreach ( $handlesToClose as $handle ) {
-                       if ( !flock( $handle, LOCK_UN ) ) {
-                               $status->fatal( 'lockmanager-fail-releaselock', $path );
-                       }
-                       if ( !fclose( $handle ) ) {
-                               $status->warning( 'lockmanager-fail-closelock', $path );
-                       }
-               }
-               return $status;
-       }
-
-       /**
-        * @param $path string
-        * @return Status
-        */
-       private function pruneKeyLockFiles( $path ) {
-               $status = Status::newGood();
-               if ( !isset( $this->locksHeld[$path] ) ) {
-                       # No locks are held for the lock file anymore
-                       if ( !unlink( $this->getLockPath( $path ) ) ) {
-                               $status->warning( 'lockmanager-fail-deletelock', $path );
-                       }
-                       unset( $this->handles[$path] );
-               }
-               return $status;
-       }
-
-       /**
-        * Get the path to the lock file for a key
-        * @param $path string
-        * @return string
-        */
-       protected function getLockPath( $path ) {
-               $hash = self::sha1Base36( $path );
-               return "{$this->lockDir}/{$hash}.lock";
-       }
-
-       /**
-        * Make sure remaining locks get cleared for sanity
-        */
-       function __destruct() {
-               while ( count( $this->locksHeld ) ) {
-                       foreach ( $this->locksHeld as $path => $locks ) {
-                               $this->doSingleUnlock( $path, self::LOCK_EX );
-                               $this->doSingleUnlock( $path, self::LOCK_SH );
-                       }
-               }
-       }
-}
diff --git a/includes/filerepo/backend/lockmanager/LSLockManager.php b/includes/filerepo/backend/lockmanager/LSLockManager.php
deleted file mode 100644 (file)
index 8942818..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-<?php
-/**
- * Version of LockManager based on using lock daemon servers.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup LockManager
- */
-
-/**
- * Manage locks using a lock daemon server.
- *
- * Version of LockManager based on using lock daemon servers.
- * This is meant for multi-wiki systems that may share files.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * All lock requests for a resource, identified by a hash string, will map
- * to one bucket. Each bucket maps to one or several peer servers, each
- * running LockServerDaemon.php, listening on a designated TCP port.
- * A majority of peers must agree for a lock to be acquired.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class LSLockManager extends QuorumLockManager {
-       /** @var Array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = array(
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       );
-
-       /** @var Array Map of server names to server config */
-       protected $lockServers; // (server name => server config array)
-
-       /** @var Array Map Server connections (server name => resource) */
-       protected $conns = array();
-
-       protected $connTimeout; // float number of seconds
-       protected $session = ''; // random SHA-1 string
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * $config paramaters include:
-        *   - lockServers  : Associative array of server names to configuration.
-        *                    Configuration is an associative array that includes:
-        *                      - host    : IP address/hostname
-        *                      - port    : TCP port
-        *                      - authKey : Secret string the lock server uses
-        *   - srvsByBucket : Array of 1-16 consecutive integer keys, starting from 0,
-        *                    each having an odd-numbered list of server names (peers) as values.
-        *   - connTimeout  : Lock server connection attempt timeout. [optional]
-        *
-        * @param Array $config
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-
-               $this->lockServers = $config['lockServers'];
-               // Sanitize srvsByBucket config to prevent PHP errors
-               $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
-               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
-
-               if ( isset( $config['connTimeout'] ) ) {
-                       $this->connTimeout = $config['connTimeout'];
-               } else {
-                       $this->connTimeout = 3; // use some sane amount
-               }
-
-               $this->session = wfRandomString( 32 ); // 128 bits
-       }
-
-       /**
-        * @see QuorumLockManager::getLocksOnServer()
-        * @return Status
-        */
-       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-
-               // Send out the command and get the response...
-               $type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
-               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
-               $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys );
-
-               if ( $response !== 'ACQUIRED' ) {
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::freeLocksOnServer()
-        * @return Status
-        */
-       protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-
-               // Send out the command and get the response...
-               $type = ( $type == self::LOCK_SH ) ? 'SH' : 'EX';
-               $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
-               $response = $this->sendCommand( $lockSrv, 'RELEASE', $type, $keys );
-
-               if ( $response !== 'RELEASED' ) {
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-releaselock', $path );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
-        */
-       protected function releaseAllLocks() {
-               $status = Status::newGood();
-
-               foreach ( $this->conns as $lockSrv => $conn ) {
-                       $response = $this->sendCommand( $lockSrv, 'RELEASE_ALL', '', array() );
-                       if ( $response !== 'RELEASED_ALL' ) {
-                               $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::isServerUp()
-        * @return bool
-        */
-       protected function isServerUp( $lockSrv ) {
-               return (bool)$this->getConnection( $lockSrv );
-       }
-
-       /**
-        * Send a command and get back the response
-        *
-        * @param $lockSrv string
-        * @param $action string
-        * @param $type string
-        * @param $values Array
-        * @return string|bool
-        */
-       protected function sendCommand( $lockSrv, $action, $type, $values ) {
-               $conn = $this->getConnection( $lockSrv );
-               if ( !$conn ) {
-                       return false; // no connection
-               }
-               $authKey = $this->lockServers[$lockSrv]['authKey'];
-               // Build of the command as a flat string...
-               $values = implode( '|', $values );
-               $key = sha1( $this->session . $action . $type . $values . $authKey );
-               // Send out the command...
-               if ( fwrite( $conn, "{$this->session}:$key:$action:$type:$values\n" ) === false ) {
-                       return false;
-               }
-               // Get the response...
-               $response = fgets( $conn );
-               if ( $response === false ) {
-                       return false;
-               }
-               return trim( $response );
-       }
-
-       /**
-        * Get (or reuse) a connection to a lock server
-        *
-        * @param $lockSrv string
-        * @return resource
-        */
-       protected function getConnection( $lockSrv ) {
-               if ( !isset( $this->conns[$lockSrv] ) ) {
-                       $cfg = $this->lockServers[$lockSrv];
-                       wfSuppressWarnings();
-                       $errno = $errstr = '';
-                       $conn = fsockopen( $cfg['host'], $cfg['port'], $errno, $errstr, $this->connTimeout );
-                       wfRestoreWarnings();
-                       if ( $conn === false ) {
-                               return null;
-                       }
-                       $sec = floor( $this->connTimeout );
-                       $usec = floor( ( $this->connTimeout - floor( $this->connTimeout ) ) * 1e6 );
-                       stream_set_timeout( $conn, $sec, $usec );
-                       $this->conns[$lockSrv] = $conn;
-               }
-               return $this->conns[$lockSrv];
-       }
-
-       /**
-        * Make sure remaining locks get cleared for sanity
-        */
-       function __destruct() {
-               $this->releaseAllLocks();
-               foreach ( $this->conns as $conn ) {
-                       fclose( $conn );
-               }
-       }
-}
diff --git a/includes/filerepo/backend/lockmanager/LockManager.php b/includes/filerepo/backend/lockmanager/LockManager.php
deleted file mode 100644 (file)
index 07853f8..0000000
+++ /dev/null
@@ -1,425 +0,0 @@
-<?php
-/**
- * @defgroup LockManager Lock management
- * @ingroup FileBackend
- */
-
-/**
- * Resource locking handling.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup LockManager
- * @author Aaron Schulz
- */
-
-/**
- * @brief Class for handling resource locking.
- *
- * Locks on resource keys can either be shared or exclusive.
- *
- * Implementations must keep track of what is locked by this proccess
- * in-memory and support nested locking calls (using reference counting).
- * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
- * Locks should either be non-blocking or have low wait timeouts.
- *
- * Subclasses should avoid throwing exceptions at all costs.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-abstract class LockManager {
-       /** @var Array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = array(
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
-               self::LOCK_EX => self::LOCK_EX
-       );
-
-       /** @var Array Map of (resource path => lock type => count) */
-       protected $locksHeld = array();
-
-       /* Lock types; stronger locks have higher values */
-       const LOCK_SH = 1; // shared lock (for reads)
-       const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
-       const LOCK_EX = 3; // exclusive lock (for writes)
-
-       /**
-        * Construct a new instance from configuration
-        *
-        * @param $config Array
-        */
-       public function __construct( array $config ) {}
-
-       /**
-        * Lock the resources at the given abstract paths
-        *
-        * @param $paths Array List of resource names
-        * @param $type integer LockManager::LOCK_* constant
-        * @return Status
-        */
-       final public function lock( array $paths, $type = self::LOCK_EX ) {
-               wfProfileIn( __METHOD__ );
-               $status = $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * Unlock the resources at the given abstract paths
-        *
-        * @param $paths Array List of storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @return Status
-        */
-       final public function unlock( array $paths, $type = self::LOCK_EX ) {
-               wfProfileIn( __METHOD__ );
-               $status = $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] );
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * Get the base 36 SHA-1 of a string, padded to 31 digits
-        *
-        * @param $path string
-        * @return string
-        */
-       final protected static function sha1Base36( $path ) {
-               return wfBaseConvert( sha1( $path ), 16, 36, 31 );
-       }
-
-       /**
-        * Lock resources with the given keys and lock type
-        *
-        * @param $paths Array List of storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @return string
-        */
-       abstract protected function doLock( array $paths, $type );
-
-       /**
-        * Unlock resources with the given keys and lock type
-        *
-        * @param $paths Array List of storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @return string
-        */
-       abstract protected function doUnlock( array $paths, $type );
-}
-
-/**
- * Self-releasing locks
- *
- * LockManager helper class to handle scoped locks, which
- * release when an object is destroyed or goes out of scope.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-class ScopedLock {
-       /** @var LockManager */
-       protected $manager;
-       /** @var Status */
-       protected $status;
-       /** @var Array List of resource paths*/
-       protected $paths;
-
-       protected $type; // integer lock type
-
-       /**
-        * @param $manager LockManager
-        * @param $paths Array List of storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @param $status Status
-        */
-       protected function __construct(
-               LockManager $manager, array $paths, $type, Status $status
-       ) {
-               $this->manager = $manager;
-               $this->paths = $paths;
-               $this->status = $status;
-               $this->type = $type;
-       }
-
-       /**
-        * Get a ScopedLock object representing a lock on resource paths.
-        * Any locks are released once this object goes out of scope.
-        * The status object is updated with any errors or warnings.
-        *
-        * @param $manager LockManager
-        * @param $paths Array List of storage paths
-        * @param $type integer LockManager::LOCK_* constant
-        * @param $status Status
-        * @return ScopedLock|null Returns null on failure
-        */
-       public static function factory(
-               LockManager $manager, array $paths, $type, Status $status
-       ) {
-               $lockStatus = $manager->lock( $paths, $type );
-               $status->merge( $lockStatus );
-               if ( $lockStatus->isOK() ) {
-                       return new self( $manager, $paths, $type, $status );
-               }
-               return null;
-       }
-
-       function __destruct() {
-               $wasOk = $this->status->isOK();
-               $this->status->merge( $this->manager->unlock( $this->paths, $this->type ) );
-               if ( $wasOk ) {
-                       // Make sure status is OK, despite any unlockFiles() fatals
-                       $this->status->setResult( true, $this->status->value );
-               }
-       }
-}
-
-/**
- * Version of LockManager that uses a quorum from peer servers for locks.
- * The resource space can also be sharded into separate peer groups.
- *
- * @ingroup LockManager
- * @since 1.20
- */
-abstract class QuorumLockManager extends LockManager {
-       /** @var Array Map of bucket indexes to peer server lists */
-       protected $srvsByBucket = array(); // (bucket index => (lsrv1, lsrv2, ...))
-
-       /**
-        * @see LockManager::doLock()
-        * @param $paths array
-        * @param $type int
-        * @return Status
-        */
-       final protected function doLock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               $pathsToLock = array(); // (bucket => paths)
-               // Get locks that need to be acquired (buckets => locks)...
-               foreach ( $paths as $path ) {
-                       if ( isset( $this->locksHeld[$path][$type] ) ) {
-                               ++$this->locksHeld[$path][$type];
-                       } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
-                               $this->locksHeld[$path][$type] = 1;
-                       } else {
-                               $bucket = $this->getBucketFromKey( $path );
-                               $pathsToLock[$bucket][] = $path;
-                       }
-               }
-
-               $lockedPaths = array(); // files locked in this attempt
-               // Attempt to acquire these locks...
-               foreach ( $pathsToLock as $bucket => $paths ) {
-                       // Try to acquire the locks for this bucket
-                       $status->merge( $this->doLockingRequestBucket( $bucket, $paths, $type ) );
-                       if ( !$status->isOK() ) {
-                               $status->merge( $this->doUnlock( $lockedPaths, $type ) );
-                               return $status;
-                       }
-                       // Record these locks as active
-                       foreach ( $paths as $path ) {
-                               $this->locksHeld[$path][$type] = 1; // locked
-                       }
-                       // Keep track of what locks were made in this attempt
-                       $lockedPaths = array_merge( $lockedPaths, $paths );
-               }
-
-               return $status;
-       }
-
-       /**
-        * @see LockManager::doUnlock()
-        * @param $paths array
-        * @param $type int
-        * @return Status
-        */
-       final protected function doUnlock( array $paths, $type ) {
-               $status = Status::newGood();
-
-               $pathsToUnlock = array();
-               foreach ( $paths as $path ) {
-                       if ( !isset( $this->locksHeld[$path][$type] ) ) {
-                               $status->warning( 'lockmanager-notlocked', $path );
-                       } else {
-                               --$this->locksHeld[$path][$type];
-                               // Reference count the locks held and release locks when zero
-                               if ( $this->locksHeld[$path][$type] <= 0 ) {
-                                       unset( $this->locksHeld[$path][$type] );
-                                       $bucket = $this->getBucketFromKey( $path );
-                                       $pathsToUnlock[$bucket][] = $path;
-                               }
-                               if ( !count( $this->locksHeld[$path] ) ) {
-                                       unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
-                               }
-                       }
-               }
-
-               // Remove these specific locks if possible, or at least release
-               // all locks once this process is currently not holding any locks.
-               foreach ( $pathsToUnlock as $bucket => $paths ) {
-                       $status->merge( $this->doUnlockingRequestBucket( $bucket, $paths, $type ) );
-               }
-               if ( !count( $this->locksHeld ) ) {
-                       $status->merge( $this->releaseAllLocks() );
-               }
-
-               return $status;
-       }
-
-       /**
-        * Attempt to acquire locks with the peers for a bucket.
-        * This is all or nothing; if any key is locked then this totally fails.
-        *
-        * @param $bucket integer
-        * @param $paths Array List of resource keys to lock
-        * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
-        * @return Status
-        */
-       final protected function doLockingRequestBucket( $bucket, array $paths, $type ) {
-               $status = Status::newGood();
-
-               $yesVotes = 0; // locks made on trustable servers
-               $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
-               $quorum = floor( $votesLeft/2 + 1 ); // simple majority
-               // Get votes for each peer, in order, until we have enough...
-               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
-                       if ( !$this->isServerUp( $lockSrv ) ) {
-                               --$votesLeft;
-                               $status->warning( 'lockmanager-fail-svr-acquire', $lockSrv );
-                               continue; // server down?
-                       }
-                       // Attempt to acquire the lock on this peer
-                       $status->merge( $this->getLocksOnServer( $lockSrv, $paths, $type ) );
-                       if ( !$status->isOK() ) {
-                               return $status; // vetoed; resource locked
-                       }
-                       ++$yesVotes; // success for this peer
-                       if ( $yesVotes >= $quorum ) {
-                               return $status; // lock obtained
-                       }
-                       --$votesLeft;
-                       $votesNeeded = $quorum - $yesVotes;
-                       if ( $votesNeeded > $votesLeft ) {
-                               break; // short-circuit
-                       }
-               }
-               // At this point, we must not have met the quorum
-               $status->setResult( false );
-
-               return $status;
-       }
-
-       /**
-        * Attempt to release locks with the peers for a bucket
-        *
-        * @param $bucket integer
-        * @param $paths Array List of resource keys to lock
-        * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
-        * @return Status
-        */
-       final protected function doUnlockingRequestBucket( $bucket, array $paths, $type ) {
-               $status = Status::newGood();
-
-               foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
-                       if ( !$this->isServerUp( $lockSrv ) ) {
-                               $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
-                       // Attempt to release the lock on this peer
-                       } else {
-                               $status->merge( $this->freeLocksOnServer( $lockSrv, $paths, $type ) );
-                       }
-               }
-
-               return $status;
-       }
-
-       /**
-        * Get the bucket for resource path.
-        * This should avoid throwing any exceptions.
-        *
-        * @param $path string
-        * @return integer
-        */
-       protected function getBucketFromKey( $path ) {
-               $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
-               return (int)base_convert( $prefix, 16, 10 ) % count( $this->srvsByBucket );
-       }
-
-       /**
-        * Check if a lock server is up
-        *
-        * @param $lockSrv string
-        * @return bool
-        */
-       abstract protected function isServerUp( $lockSrv );
-
-       /**
-        * Get a connection to a lock server and acquire locks on $paths
-        *
-        * @param $lockSrv string
-        * @param $paths array
-        * @param $type integer
-        * @return Status
-        */
-       abstract protected function getLocksOnServer( $lockSrv, array $paths, $type );
-
-       /**
-        * Get a connection to a lock server and release locks on $paths.
-        *
-        * Subclasses must effectively implement this or releaseAllLocks().
-        *
-        * @param $lockSrv string
-        * @param $paths array
-        * @param $type integer
-        * @return Status
-        */
-       abstract protected function freeLocksOnServer( $lockSrv, array $paths, $type );
-
-       /**
-        * Release all locks that this session is holding.
-        *
-        * Subclasses must effectively implement this or freeLocksOnServer().
-        *
-        * @return Status
-        */
-       abstract protected function releaseAllLocks();
-}
-
-/**
- * Simple version of LockManager that does nothing
- * @since 1.19
- */
-class NullLockManager extends LockManager {
-       /**
-        * @see LockManager::doLock()
-        * @param $paths array
-        * @param $type int
-        * @return Status
-        */
-       protected function doLock( array $paths, $type ) {
-               return Status::newGood();
-       }
-
-       /**
-        * @see LockManager::doUnlock()
-        * @param $paths array
-        * @param $type int
-        * @return Status
-        */
-       protected function doUnlock( array $paths, $type ) {
-               return Status::newGood();
-       }
-}
diff --git a/includes/filerepo/backend/lockmanager/LockManagerGroup.php b/includes/filerepo/backend/lockmanager/LockManagerGroup.php
deleted file mode 100644 (file)
index 8c8c940..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-/**
- * Lock manager registration handling.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup LockManager
- */
-
-/**
- * Class to handle file lock manager registration
- *
- * @ingroup LockManager
- * @author Aaron Schulz
- * @since 1.19
- */
-class LockManagerGroup {
-       /**
-        * @var LockManagerGroup
-        */
-       protected static $instance = null;
-
-       /** @var Array of (name => ('class' =>, 'config' =>, 'instance' =>)) */
-       protected $managers = array();
-
-       protected function __construct() {}
-
-       /**
-        * @return LockManagerGroup
-        */
-       public static function singleton() {
-               if ( self::$instance == null ) {
-                       self::$instance = new self();
-                       self::$instance->initFromGlobals();
-               }
-               return self::$instance;
-       }
-
-       /**
-        * Destroy the singleton instance, so that a new one will be created next
-        * time singleton() is called.
-        */
-       public static function destroySingleton() {
-               self::$instance = null;
-       }
-
-       /**
-        * Register lock managers from the global variables
-        *
-        * @return void
-        */
-       protected function initFromGlobals() {
-               global $wgLockManagers;
-
-               $this->register( $wgLockManagers );
-       }
-
-       /**
-        * Register an array of file lock manager configurations
-        *
-        * @param $configs Array
-        * @return void
-        * @throws MWException
-        */
-       protected function register( array $configs ) {
-               foreach ( $configs as $config ) {
-                       if ( !isset( $config['name'] ) ) {
-                               throw new MWException( "Cannot register a lock manager with no name." );
-                       }
-                       $name = $config['name'];
-                       if ( !isset( $config['class'] ) ) {
-                               throw new MWException( "Cannot register lock manager `{$name}` with no class." );
-                       }
-                       $class = $config['class'];
-                       unset( $config['class'] ); // lock manager won't need this
-                       $this->managers[$name] = array(
-                               'class'    => $class,
-                               'config'   => $config,
-                               'instance' => null
-                       );
-               }
-       }
-
-       /**
-        * Get the lock manager object with a given name
-        *
-        * @param $name string
-        * @return LockManager
-        * @throws MWException
-        */
-       public function get( $name ) {
-               if ( !isset( $this->managers[$name] ) ) {
-                       throw new MWException( "No lock manager defined with the name `$name`." );
-               }
-               // Lazy-load the actual lock manager instance
-               if ( !isset( $this->managers[$name]['instance'] ) ) {
-                       $class = $this->managers[$name]['class'];
-                       $config = $this->managers[$name]['config'];
-                       $this->managers[$name]['instance'] = new $class( $config );
-               }
-               return $this->managers[$name]['instance'];
-       }
-
-       /**
-        * Get the default lock manager configured for the site.
-        * Returns NullLockManager if no lock manager could be found.
-        *
-        * @return LockManager
-        */
-       public function getDefault() {
-               return isset( $this->managers['default'] )
-                       ? $this->get( 'default' )
-                       : new NullLockManager( array() );
-       }
-
-       /**
-        * Get the default lock manager configured for the site
-        * or at least some other effective configured lock manager.
-        * Throws an exception if no lock manager could be found.
-        *
-        * @return LockManager
-        * @throws MWException
-        */
-       public function getAny() {
-               return isset( $this->managers['default'] )
-                       ? $this->get( 'default' )
-                       : $this->get( 'fsLockManager' );
-       }
-}
diff --git a/includes/filerepo/backend/lockmanager/MemcLockManager.php b/includes/filerepo/backend/lockmanager/MemcLockManager.php
deleted file mode 100644 (file)
index 9e81dbf..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-/**
- * Version of LockManager based on using memcached servers.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup LockManager
- */
-
-/**
- * Manage locks using memcached servers.
- *
- * Version of LockManager based on using memcached servers.
- * This is meant for multi-wiki systems that may share files.
- * All locks are non-blocking, which avoids deadlocks.
- *
- * All lock requests for a resource, identified by a hash string, will map
- * to one bucket. Each bucket maps to one or several peer servers, each running memcached.
- * A majority of peers must agree for a lock to be acquired.
- *
- * @ingroup LockManager
- * @since 1.20
- */
-class MemcLockManager extends QuorumLockManager {
-       /** @var Array Mapping of lock types to the type actually used */
-       protected $lockTypeMap = array(
-               self::LOCK_SH => self::LOCK_SH,
-               self::LOCK_UW => self::LOCK_SH,
-               self::LOCK_EX => self::LOCK_EX
-       );
-
-       /** @var Array Map server names to MemcachedBagOStuff objects */
-       protected $bagOStuffs = array();
-       /** @var Array */
-       protected $serversUp = array(); // (server name => bool)
-
-       protected $lockExpiry; // integer; maximum time locks can be held
-       protected $session = ''; // string; random SHA-1 UUID
-       protected $wikiId = ''; // string
-
-       /**
-        * Construct a new instance from configuration.
-        *
-        * $config paramaters include:
-        *   - 'lockServers'  : Associative array of server names to "<IP>:<port>" strings.
-        *   - 'srvsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
-        *                      each having an odd-numbered list of server names (peers) as values.
-        *   - 'memcConfig'   : Configuration array for ObjectCache::newFromParams. [optional]
-        *                      If set, this must use one of the memcached classes.
-        *   - 'wikiId'       : Wiki ID string that all resources are relative to. [optional]
-        *
-        * @param Array $config
-        */
-       public function __construct( array $config ) {
-               parent::__construct( $config );
-
-               // Sanitize srvsByBucket config to prevent PHP errors
-               $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
-               $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
-
-               $memcConfig = isset( $config['memcConfig'] )
-                       ? $config['memcConfig']
-                       : array( 'class' => 'MemcachedPhpBagOStuff' );
-
-               foreach ( $config['lockServers'] as $name => $address ) {
-                       $params = array( 'servers' => array( $address ) ) + $memcConfig;
-                       $cache = ObjectCache::newFromParams( $params );
-                       if ( $cache instanceof MemcachedBagOStuff ) {
-                               $this->bagOStuffs[$name] = $cache;
-                       } else {
-                               throw new MWException(
-                                       'Only MemcachedBagOStuff classes are supported by MemcLockManager.' );
-                       }
-               }
-
-               $this->wikiId = isset( $config['wikiId'] ) ? $config['wikiId'] : wfWikiID();
-
-               $met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
-               $this->lockExpiry = $met ? 2*(int)$met : 2*3600;
-
-               $this->session = wfRandomString( 32 );
-       }
-
-       /**
-        * @see QuorumLockManager::getLocksOnServer()
-        * @return Status
-        */
-       protected function getLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-
-               $memc = $this->getCache( $lockSrv );
-               $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
-
-               // Lock all of the active lock record keys...
-               if ( !$this->acquireMutexes( $memc, $keys ) ) {
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                       }
-                       return;
-               }
-
-               // Fetch all the existing lock records...
-               $lockRecords = $memc->getMulti( $keys );
-
-               $now = time();
-               // Check if the requested locks conflict with existing ones...
-               foreach ( $paths as $path ) {
-                       $locksKey = $this->recordKeyForPath( $path );
-                       $locksHeld = isset( $lockRecords[$locksKey] )
-                               ? $lockRecords[$locksKey]
-                               : array( self::LOCK_SH => array(), self::LOCK_EX => array() ); // init
-                       foreach ( $locksHeld[self::LOCK_EX] as $session => $expiry ) {
-                               if ( $expiry < $now ) { // stale?
-                                       unset( $locksHeld[self::LOCK_EX][$session] );
-                               } elseif ( $session !== $this->session ) {
-                                       $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                               }
-                       }
-                       if ( $type === self::LOCK_EX ) {
-                               foreach ( $locksHeld[self::LOCK_SH] as $session => $expiry ) {
-                                       if ( $expiry < $now ) { // stale?
-                                               unset( $locksHeld[self::LOCK_SH][$session] );
-                                       } elseif ( $session !== $this->session ) {
-                                               $status->fatal( 'lockmanager-fail-acquirelock', $path );
-                                       }
-                               }
-                       }
-                       if ( $status->isOK() ) {
-                               // Register the session in the lock record array
-                               $locksHeld[$type][$this->session] = $now + $this->lockExpiry;
-                               // We will update this record if none of the other locks conflict
-                               $lockRecords[$locksKey] = $locksHeld;
-                       }
-               }
-
-               // If there were no lock conflicts, update all the lock records...
-               if ( $status->isOK() ) {
-                       foreach ( $lockRecords as $locksKey => $locksHeld ) {
-                               $memc->set( $locksKey, $locksHeld );
-                               wfDebug( __METHOD__ . ": acquired lock on key $locksKey.\n" );
-                       }
-               }
-
-               // Unlock all of the active lock record keys...
-               $this->releaseMutexes( $memc, $keys );
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::freeLocksOnServer()
-        * @return Status
-        */
-       protected function freeLocksOnServer( $lockSrv, array $paths, $type ) {
-               $status = Status::newGood();
-
-               $memc = $this->getCache( $lockSrv );
-               $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
-
-               // Lock all of the active lock record keys...
-               if ( !$this->acquireMutexes( $memc, $keys ) ) {
-                       foreach ( $paths as $path ) {
-                               $status->fatal( 'lockmanager-fail-releaselock', $path );
-                       }
-                       return;
-               }
-
-               // Fetch all the existing lock records...
-               $lockRecords = $memc->getMulti( $keys );
-
-               // Remove the requested locks from all records...
-               foreach ( $paths as $path ) {
-                       $locksKey = $this->recordKeyForPath( $path ); // lock record
-                       if ( !isset( $lockRecords[$locksKey] ) ) {
-                               continue; // nothing to do
-                       }
-                       $locksHeld = $lockRecords[$locksKey];
-                       if ( is_array( $locksHeld ) && isset( $locksHeld[$type] ) ) {
-                               unset( $locksHeld[$type][$this->session] );
-                               $ok = $memc->set( $locksKey, $locksHeld );
-                       } else {
-                               $ok = true;
-                       }
-                       if ( !$ok ) {
-                               $status->fatal( 'lockmanager-fail-releaselock', $path );
-                       }
-                       wfDebug( __METHOD__ . ": released lock on key $locksKey.\n" );
-               }
-
-               // Unlock all of the active lock record keys...
-               $this->releaseMutexes( $memc, $keys );
-
-               return $status;
-       }
-
-       /**
-        * @see QuorumLockManager::releaseAllLocks()
-        * @return Status
-        */
-       protected function releaseAllLocks() {
-               return Status::newGood(); // not supported
-       }
-
-       /**
-        * @see QuorumLockManager::isServerUp()
-        * @return bool
-        */
-       protected function isServerUp( $lockSrv ) {
-               return (bool)$this->getCache( $lockSrv );
-       }
-
-       /**
-        * Get the MemcachedBagOStuff object for a $lockSrv
-        *
-        * @param $lockSrv string Server name
-        * @return MemcachedBagOStuff|null
-        */
-       protected function getCache( $lockSrv ) {
-               $memc = null;
-               if ( isset( $this->bagOStuffs[$lockSrv] ) ) {
-                       $memc = $this->bagOStuffs[$lockSrv];
-                       if ( !isset( $this->serversUp[$lockSrv] ) ) {
-                               $this->serversUp[$lockSrv] = $memc->set( 'MemcLockManager:ping', 1, 1 );
-                               if ( !$this->serversUp[$lockSrv] ) {
-                                       trigger_error( __METHOD__ . ": Could not contact $lockSrv.", E_USER_WARNING );
-                               }
-                       }
-                       if ( !$this->serversUp[$lockSrv] ) {
-                               return null; // server appears to be down
-                       }
-               }
-               return $memc;
-       }
-
-       /**
-        * @param $path string
-        * @return string
-        */
-       protected function recordKeyForPath( $path ) {
-               $hash = LockManager::sha1Base36( $path );
-               list( $db, $prefix ) = wfSplitWikiID( $this->wikiId );
-               return wfForeignMemcKey( $db, $prefix, __CLASS__, 'locks', $hash );
-       }
-
-       /**
-        * @param $memc MemcachedBagOStuff
-        * @param $keys Array List of keys to acquire
-        * @return bool
-        */
-       protected function acquireMutexes( MemcachedBagOStuff $memc, array $keys ) {
-               $lockedKeys = array();
-
-               // Acquire the keys in lexicographical order, to avoid deadlock problems.
-               // If P1 is waiting to acquire a key P2 has, P2 can't also be waiting for a key P1 has.
-               sort( $keys );
-
-               // Try to quickly loop to acquire the keys, but back off after a few rounds.
-               // This reduces memcached spam, especially in the rare case where a server acquires
-               // some lock keys and dies without releasing them. Lock keys expire after a few minutes.
-               $rounds = 0;
-               $start = microtime( true );
-               do {
-                       if ( ( ++$rounds % 4 ) == 0 ) {
-                               usleep( 1000*50 ); // 50 ms
-                       }
-                       foreach ( array_diff( $keys, $lockedKeys ) as $key ) {
-                               if ( $memc->add( "$key:mutex", 1, 180 ) ) { // lock record
-                                       $lockedKeys[] = $key;
-                               } else {
-                                       continue; // acquire in order
-                               }
-                       }
-               } while ( count( $lockedKeys ) < count( $keys ) && ( microtime( true ) - $start ) <= 6 );
-
-               if ( count( $lockedKeys ) != count( $keys ) ) {
-                       $this->releaseMutexes( $lockedKeys ); // failed; release what was locked
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * @param $memc MemcachedBagOStuff
-        * @param $keys Array List of acquired keys
-        * @return void
-        */
-       protected function releaseMutexes( MemcachedBagOStuff $memc, array $keys ) {
-               foreach ( $keys as $key ) {
-                       $memc->delete( "$key:mutex" );
-               }
-       }
-
-       /**
-        * Make sure remaining locks get cleared for sanity
-        */
-       function __destruct() {
-               while ( count( $this->locksHeld ) ) {
-                       foreach ( $this->locksHeld as $path => $locks ) {
-                               $this->doUnlock( array( $path ), self::LOCK_EX );
-                               $this->doUnlock( array( $path ), self::LOCK_SH );
-                       }
-               }
-       }
-}
index 2cc539b..0beedfa 100644 (file)
@@ -273,7 +273,7 @@ abstract class DatabaseInstaller {
                        $up = DatabaseUpdater::newForDB( $this->db );
                        $up->doUpdates();
                } catch ( MWException $e ) {
-                       echo "\nAn error occured:\n";
+                       echo "\nAn error occurred:\n";
                        echo $e->getText();
                        $ret = false;
                }
index e722893..a107cec 100644 (file)
@@ -627,6 +627,8 @@ Add dir="ltr" to the <nowiki><code></nowiki> for right-to-left languages.',
        'config-type-postgres' => '{{optional}}',
        'config-type-sqlite' => '{{optional}}',
        'config-type-oracle' => '{{optional}}',
+       'config-support-info' => 'Parameters:
+* $1 - a list of DBMSs that MediaWiki supports, composed with other config-type-* and config-support-* messages.',
        'config-support-mysql' => 'Parameters:
 * $1 - a link to the MySQL home page having the anchor text "MySQL".',
        'config-support-postgres' => 'Parameters:
index c10fe88..917a59a 100644 (file)
@@ -457,7 +457,7 @@ class WebInstaller_DBConnect extends WebInstallerPage {
                        $dbSupport .= wfMsgNoTrans( "config-support-$type", $link ) . "\n";
                }
                $this->addHTML( $this->parent->getInfoBox(
-                       wfMsg( 'config-support-info', $dbSupport ) ) );
+                       wfMsg( 'config-support-info', trim( $dbSupport ) ) ) );
 
                foreach ( $this->parent->getVar( '_CompiledDBs' ) as $type ) {
                        $installer = $this->parent->getDBInstaller( $type );
index d3cc550..bbcc185 100644 (file)
@@ -41,10 +41,7 @@ class FormatJson {
         * @return string
         */
        public static function encode( $value, $isHtml = false ) {
-               // Some versions of PHP have a broken json_encode, see PHP bug
-               // 46944. Test encoding an affected character (U+20000) to
-               // avoid this.
-               if ( !function_exists( 'json_encode' ) || $isHtml || strtolower( json_encode( "\xf0\xa0\x80\x80" ) ) != '"\ud840\udc00"' ) {
+               if ( !function_exists( 'json_encode' ) || $isHtml ) {
                        $json = new Services_JSON();
                        return $json->encode( $value, $isHtml );
                } else {
index 56c5842..0f30c6f 100644 (file)
@@ -274,7 +274,7 @@ class BitmapHandler extends ImageHandler {
         * @param $image File File associated with this thumbnail
         * @param $params array Array with scaler params
         *
-        * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+        * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
         */
        protected function transformImageMagick( $image, $params ) {
                # use ImageMagick
@@ -373,7 +373,7 @@ class BitmapHandler extends ImageHandler {
         * @param $image File File associated with this thumbnail
         * @param $params array Array with scaler params
         *
-        * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+        * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
         */
        protected function transformImageMagickExt( $image, $params ) {
                global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea;
@@ -450,7 +450,7 @@ class BitmapHandler extends ImageHandler {
         * @param $image File File associated with this thumbnail
         * @param $params array Array with scaler params
         *
-        * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+        * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
         */
        protected function transformCustom( $image, $params ) {
                # Use a custom convert command
@@ -477,7 +477,7 @@ class BitmapHandler extends ImageHandler {
        }
 
        /**
-        * Log an error that occured in an external process
+        * Log an error that occurred in an external process
         *
         * @param $retval int
         * @param $err int
@@ -506,7 +506,7 @@ class BitmapHandler extends ImageHandler {
         * @param $image File File associated with this thumbnail
         * @param $params array Array with scaler params
         *
-        * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+        * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
         */
        protected function transformGd( $image, $params ) {
                # Use PHP's builtin GD library functions.
index 66da408..b351290 100644 (file)
@@ -735,16 +735,22 @@ class Parser {
        /**
         * Get the target language for the content being parsed. This is usually the
         * language that the content is in.
+        *
+        * @since 1.19
+        *
+        * @return Language|null
         */
-       function getTargetLanguage() {
+       public function getTargetLanguage() {
                $target = $this->mOptions->getTargetLanguage();
+
                if ( $target !== null ) {
                        return $target;
                } elseif( $this->mOptions->getInterfaceMessage() ) {
                        return $this->mOptions->getUserLangObj();
                } elseif( is_null( $this->mTitle ) ) {
-                       throw new MWException( __METHOD__.': $this->mTitle is null' );
+                       throw new MWException( __METHOD__ . ': $this->mTitle is null' );
                }
+
                return $this->mTitle->getPageLanguage();
        }
 
index f5e1134..b3f6c72 100644 (file)
@@ -152,7 +152,9 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
         * @since 1.20
         */
        public function saveCache() {
-               $this->cacheHelper->saveCache();
+               if ( isset( $this->cacheHelper ) ) {
+                       $this->cacheHelper->saveCache();
+               }
        }
 
        /**
index 2ab02cb..3d6033e 100644 (file)
@@ -66,14 +66,14 @@ class SpecialEmailUser extends UnlistedSpecialPage {
                                'label-message' => 'emailsubject',
                                'maxlength' => 200,
                                'size' => 60,
-                               'required' => 1,
+                               'required' => true,
                        ),
                        'Text' => array(
                                'type' => 'textarea',
                                'rows' => 20,
                                'cols' => 80,
                                'label-message' => 'emailmessage',
-                               'required' => 1,
+                               'required' => true,
                        ),
                        'CCMe' => array(
                                'type' => 'check',
index 0e647a9..ba3a05c 100644 (file)
@@ -63,6 +63,10 @@ abstract class UploadBase {
        const WINDOWS_NONASCII_FILENAME = 13;
        const FILENAME_TOO_LONG = 14;
 
+       /**
+        * @param $error int
+        * @return string
+        */
        public function getVerificationErrorCode( $error ) {
                $code_to_status = array(self::EMPTY_FILE => 'empty-file',
                                                                self::FILE_TOO_LARGE => 'file-too-large',
@@ -165,6 +169,7 @@ abstract class UploadBase {
 
        /**
         * Check whether a request if valid for this handler
+        * @param $request
         * @return bool
         */
        public static function isValidRequest( $request ) {
@@ -187,7 +192,7 @@ abstract class UploadBase {
         * @param $tempPath string the temporary path
         * @param $fileSize int the file size
         * @param $removeTempFile bool (false) remove the temporary file?
-        * @return null
+        * @throws MWException
         */
        public function initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile = false ) {
                $this->mDesiredDestName = $name;
@@ -230,7 +235,7 @@ abstract class UploadBase {
 
        /**
         * @param $srcPath String: the source path
-        * @return stringthe real path if it was a virtual URL
+        * @return string the real path if it was a virtual URL
         */
        function getRealPath( $srcPath ) {
                wfProfileIn( __METHOD__ );
@@ -761,7 +766,7 @@ abstract class UploadBase {
        /**
         * Return the local file and initializes if necessary.
         *
-        * @return LocalFile
+        * @return LocalFile|null
         */
        public function getLocalFile() {
                if( is_null( $this->mLocalFile ) ) {
@@ -833,6 +838,7 @@ abstract class UploadBase {
         * earlier pseudo-'extensions' to determine type and execute
         * scripts, so the blacklist needs to check them all.
         *
+        * @param $filename string
         * @return array
         */
        public static function splitExtensions( $filename ) {
@@ -1031,6 +1037,10 @@ abstract class UploadBase {
                return false;
        }
 
+       /**
+        * @param $filename string
+        * @return bool
+        */
        protected function detectScriptInSvg( $filename ) {
                $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) );
                return $check->filterMatch;
@@ -1038,6 +1048,8 @@ abstract class UploadBase {
 
        /**
         * @todo Replace this with a whitelist filter!
+        * @param $element string
+        * @param $attribs array
         * @return bool
         */
        public function checkSvgScriptCallback( $element, $attribs ) {
@@ -1137,6 +1149,10 @@ abstract class UploadBase {
                return false; //No scripts detected
        }
 
+       /**
+        * @param $name string
+        * @return string
+        */
        private function stripXmlNamespace( $name ) {
                // 'http://www.w3.org/2000/svg:script' -> 'script'
                $parts = explode( ':', strtolower( $name ) );
@@ -1388,6 +1404,7 @@ abstract class UploadBase {
 
        /**
         * Helper function that checks whether the filename looks like a thumbnail
+        * @param $filename string
         * @return bool
         */
        public static function isThumbName( $filename ) {
@@ -1451,13 +1468,20 @@ abstract class UploadBase {
                return $info;
        }
 
-
+       /**
+        * @param $error array
+        * @return Status
+        */
        public function convertVerifyErrorToStatus( $error ) {
                $code = $error['status'];
                unset( $code['status'] );
                return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
        }
 
+       /**
+        * @param $forType null|string
+        * @return int
+        */
        public static function getMaxUploadSize( $forType = null ) {
                global $wgMaxUploadSize;
 
index 87c798b..54a68af 100644 (file)
  */
 class UploadFromChunks extends UploadFromFile {
        protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath;
-       
+
        /**
         * Setup local pointers to stash, repo and user ( similar to UploadFromStash )
-        * 
+        *
         * @param $user User
         * @param $stash UploadStash
         * @param $repo FileRepo
@@ -82,7 +82,7 @@ class UploadFromChunks extends UploadFromFile {
                $this->updateChunkStatus();
                return $this->mLocalFile;
        }
-       
+
        /**
         * Continue chunk uploading
         */     
@@ -201,7 +201,7 @@ class UploadFromChunks extends UploadFromFile {
        private function updateChunkStatus(){
                wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" . 
                                        $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
-               
+
                $dbw = $this->repo->getMasterDb();
                $dbw->update(
                        'uploadstash',
@@ -264,8 +264,9 @@ class UploadFromChunks extends UploadFromFile {
 
        /**
         * Output the chunk to disk
-        * 
+        *
         * @param $chunkPath string
+        * @throws UploadChunkFileException
         * @return FileRepoStatus
         */
        private function outputChunk( $chunkPath ){
@@ -291,6 +292,7 @@ class UploadFromChunks extends UploadFromFile {
                }
                return $storeStatus;
        }
+
        private function getChunkFileKey( $index = null ){
                if( $index === null ){
                        $index = $this->getChunkIndex();
index fae9974..aa0cc77 100644 (file)
@@ -36,26 +36,25 @@ class UploadFromFile extends UploadBase {
 
        /**
         * @param $request WebRequest
-        * @return null
         */
        function initializeFromRequest( &$request ) {
-               $upload = $request->getUpload( 'wpUploadFile' );                
+               $upload = $request->getUpload( 'wpUploadFile' );
                $desiredDestName = $request->getText( 'wpDestFile' );
-               if( !$desiredDestName )
+               if( !$desiredDestName ) {
                        $desiredDestName = $upload->getName();
-                       
-               return $this->initialize( $desiredDestName, $upload );
+               }
+
+               $this->initialize( $desiredDestName, $upload );
        }
 
        /**
         * Initialize from a filename and a WebRequestUpload
         * @param $name
         * @param $webRequestUpload
-        * @return null
         */
        function initialize( $name, $webRequestUpload ) {
                $this->mUpload = $webRequestUpload;
-               return $this->initializePathInfo( $name, 
+               $this->initializePathInfo( $name,
                        $this->mUpload->getTempName(), $this->mUpload->getSize() );
        }
 
index d5cce14..607965f 100644 (file)
@@ -62,8 +62,6 @@ class UploadFromStash extends UploadBase {
 
                        $this->stash = new UploadStash( $this->repo, $this->user );
                }
-
-               return true;
        }
 
        /**
index a9e4519..c11d719 100644 (file)
@@ -41,7 +41,7 @@ class UploadFromUrl extends UploadBase {
         *
         * @param $user User
         *
-        * @return true|string
+        * @return bool|string
         */
        public static function isAllowed( $user ) {
                if ( !$user->isAllowed( 'upload_by_url' ) ) {
@@ -92,6 +92,7 @@ class UploadFromUrl extends UploadBase {
         * @param $async mixed Whether the download should be performed
         * asynchronous. False for synchronous, async or async-leavemessage for
         * asynchronous download.
+        * @throws MWException
         */
        public function initialize( $name, $url, $async = false ) {
                global $wgAllowAsyncCopyUploads;
@@ -256,6 +257,7 @@ class UploadFromUrl extends UploadBase {
        /**
         * Wrapper around the parent function in order to defer checking protection
         * until we are sure that the file can actually be uploaded
+        * @param $user User
         * @return bool|mixed
         */
        public function verifyTitlePermissions( $user ) {
@@ -268,6 +270,10 @@ class UploadFromUrl extends UploadBase {
        /**
         * Wrapper around the parent function in order to defer uploading to the
         * job queue for asynchronous uploads
+        * @param $comment string
+        * @param $pageText string
+        * @param $watch bool
+        * @param $user User
         * @return Status
         */
        public function performUpload( $comment, $pageText, $watch, $user ) {
@@ -281,11 +287,11 @@ class UploadFromUrl extends UploadBase {
        }
 
        /**
-        * @param  $comment
-        * @param  $pageText
-        * @param  $watch
-        * @param  $user User
-        * @return
+        * @param $comment
+        * @param $pageText
+        * @param $watch
+        * @param $user User
+        * @return String
         */
        protected function insertJob( $comment, $pageText, $watch, $user ) {
                $sessionKey = $this->stashSession();
index bbee0f5..aa65ffe 100644 (file)
@@ -239,7 +239,8 @@ class UploadStash {
                                }
                        }
                        // at this point, $error should contain the single "most important" error, plus any parameters.
-                       throw new UploadStashFileException( "Error storing file in '$path': " . wfMessage( $error )->text() );
+                       $errorMsg = array_shift( $error );
+                       throw new UploadStashFileException( "Error storing file in '$path': " . wfMessage( $errorMsg, $error )->text() );
                }
                $stashPath = $storeStatus->value;
 
index 6c0f1f6..e0f45cd 100644 (file)
@@ -92,6 +92,7 @@ class UzConverter extends LanguageConverter {
                'z' => 'з', 'Z' => 'З',
                'j' => 'ж', 'J' => 'Ж',
                'o‘' => 'ў', 'O‘' => 'Ў', 'oʻ' => 'ў', 'Oʻ' => 'Ў',
+               'yo‘' => 'йў', 'Yo‘' => 'Йў', 'yoʻ' => 'йў', 'Yoʻ' => 'Йў',
                'ts' => 'ц', 'Ts' => 'Ц',
                'q' => 'қ', 'Q' => 'Қ',
                'yo' => 'ё', 'Yo' => 'Ё',
index a532aa2..2e2ed5e 100644 (file)
@@ -951,20 +951,60 @@ $1",
 'revdelete-otherreason' => 'سبب آخر/إضافي:',
 'revdelete-reasonotherlist' => 'سبب آخر',
 'revdelete-edit-reasonlist' => 'عدل أسباب الحذف',
+'revdelete-offender' => 'مؤلف المراجعة:',
+
+# Suppression log
+'suppressionlog' => 'سجل الإخفاء',
+'suppressionlogtext' => 'بالأسفل قائمة بعمليات الحذف والمنع التي تتضمن محتوى مخفيا عن الإداريين.
+انظر [[Special:BlockList|قائمة منع الآيبي]] لترى عمليات المنع القائمة الآن.',
+
+# History merging
+'mergehistory' => 'دمج تواريخ الصفحة',
+'mergehistory-header' => 'هذه الصفحة تسمح لك بدمج نسخ تاريخ صفحة ما إلى صفحة أخرى.
+تأكد من أن هذا التغيير سيحافظ على استمرار تاريخ الصفحة.',
+'mergehistory-box' => 'دمج مراجعات صفحتين:',
+'mergehistory-from' => 'الصفحة المصدر:',
+'mergehistory-into' => 'الصفحة الهدف:',
+'mergehistory-list' => 'تاريخ التعديل القابل للدمج',
+'mergehistory-merge' => 'المراجعات التالية من [[:$1]] يمكن دمجها إلى [[:$2]].
+استخدم عامود الصناديق لدمج المراجعات التي تم إنشاؤها في وقبل الوقت المحدد.
+لاحظ أن استخدام وصلات التصفح سيعيد ضبط هذا العامود.',
+'mergehistory-go' => 'عرض التعديلات القابلة للدمج',
+'mergehistory-submit' => 'دمج المراجعات',
+'mergehistory-empty' => 'لا مراجعات يمكن دمجها.',
+'mergehistory-success' => '$3 {{PLURAL:$3|مراجعة|مراجعة}} من [[:$1]] تم دمجها بنجاح في [[:$2]].',
+'mergehistory-fail' => 'غير قادر على عمل دمج التاريخ، من فضلك أعد التحقق من محددات الصفحة والزمن.',
+'mergehistory-no-source' => 'الصفحة المصدر $1 غير موجودة.',
+'mergehistory-no-destination' => 'الصفحة الهدف $1 غير موجودة.',
+'mergehistory-invalid-source' => 'الصفحة المصدر يجب أن تكون عنوانا صحيحا.',
+'mergehistory-invalid-destination' => 'الصفحة الهدف يجب أن تكون عنوانا صحيحا.',
+'mergehistory-autocomment' => 'دمج [[:$1]] في [[:$2]]',
+'mergehistory-comment' => 'دمج [[:$1]] في [[:$2]]: $3',
+'mergehistory-same-destination' => 'صفحتا المصدر والهدف لا يمكن أن تكونا نفس الشيء',
+'mergehistory-reason' => 'السبب:',
 
 # Merge log
+'mergelog' => 'سجل الدمج',
+'pagemerge-logentry' => 'دمج [[$1]] إلى [[$2]] (المراجعات حتى $3)',
 'revertmerge' => 'إلغاء الدمج',
+'mergelogpagetext' => 'بالأسفل قائمة بأحدث عمليات الدمج لتاريخ صفحة ما إلى أخرى.',
 
 # Diffs
 'history-title' => ' «$1»: تاريخ المراجعة',
+'difference-title' => '«$1»: الفرق بين المراجعتين',
+'difference-title-multipage' => '«$1» و«$2»: الفرق بين الصفحتين',
+'difference-multipage' => '(الفرق بين الصفحتين)',
 'lineno' => 'سطر $1:',
 'compareselectedversions' => 'قارن بين النسختين المختارتين',
+'showhideselectedversions' => 'أظهر/أخف المراجعات المختارة',
 'editundo' => 'تراجع',
 'diff-multi' => '({{PLURAL:$1||مراجعة واحدة متوسطة غير معروضة أجراها|مراجعتان متوسطتان غير معروضتين أجراهما|$1 مراجعات متوسطة غير معروضة أجراها|$1 مراجعة متوسطة غير معروضة أجراها}} {{PLURAL:$2||مستخدم واحد|مستخدمان|$2 مستخدمين|$2 مستخدمًا|$2 مستخدم}}.)',
+'diff-multi-manyusers' => '({{PLURAL:$1||مراجعة واحدة متوسطة غير معروضة أجراها|مراجعتان متوسطتان غير معروضتان أجراهما|$1 مراجعات متوسطة غير معروضة أجراها|$1 مراجعة متوسطة غير معروضة أجراها}} أكثر من {{PLURAL:$2||مستخدم واحد|مستخدمين|$2 مستخدمين|$2 مستخدمًا|$2 مستخدم}}.)',
 
 # Search results
 'searchresults' => 'el resultats',
 'searchresults-title' => 'نتائج البحث عن "$1"',
+'searchresulttext' => 'للمزيد من المعلومات حول البحث في {{SITENAME}}، انظر [[{{MediaWiki:Helppage}}|{{int:help}}]].',
 'prevn' => '{{PLURAL:$1|$1}} السابقة',
 'nextn' => '{{PLURAL:$1|$1}} التالية',
 'prevn-title' => '$1 {{PLURAL:$1|نتيجة|نتيجة}} سابقة',
index 8f263d8..61baea7 100644 (file)
@@ -215,7 +215,7 @@ $messages = array(
 'tog-hidepatrolled' => 'Versteek gepatrolleerde wysigings in onlangse wysigingslys',
 'tog-newpageshidepatrolled' => 'Versteek gepatrolleerde wysigings van nuwe bladsy lys',
 'tog-extendwatchlist' => 'Brei dophoulys uit om alle wysigings te wys, nie slegs die nuutste nie',
-'tog-usenewrc' => 'Verbeterde onlangse wysigingslys (benodig JavaScript)',
+'tog-usenewrc' => 'Groepeer wysigings per bladsy in onlangse wysigings en dophoulys (benodig JavaScript)',
 'tog-numberheadings' => 'Nommer opskrifte outomaties',
 'tog-showtoolbar' => 'Wys redigeergereedskap (benodig JavaScript)',
 'tog-editondblclick' => 'Dubbelkliek om blaaie te wysig (benodig JavaScript)',
@@ -223,17 +223,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Wysig afdeling met regskliek op afdeling se titel (JavaScript)',
 'tog-showtoc' => 'Wys inhoudsopgawe (by bladsye met meer as drie opskrifte)',
 'tog-rememberpassword' => 'Onthou dat ek op hierdie rekenaar ingeteken het (vir \'n maksimum van $1 {{PLURAL:$|dag|dae}})',
-'tog-watchcreations' => 'Voeg bladsye wat ek skep by my dophoulys',
-'tog-watchdefault' => 'Lys nuwe en gewysigde bladsye.',
-'tog-watchmoves' => 'Voeg die bladsye wat ek skuif by my dophoulys',
-'tog-watchdeletion' => 'Voeg bladsye wat ek verwyder by my dophoulys',
+'tog-watchcreations' => 'Voeg bladsye wat ek skep en lêers wat ek oplaai by my dophoulys',
+'tog-watchdefault' => 'Voeg bladsye en lêers wat ek wysig by my dophoulys',
+'tog-watchmoves' => 'Voeg bladsye en lêers wat ek skuif by my dophoulys',
+'tog-watchdeletion' => 'Voeg bladsye en lêers wat ek skrap by my dophoulys',
 'tog-minordefault' => 'Merk alle wysigings automaties as klein by verstek.',
 'tog-previewontop' => 'Wys voorskou bo wysigingsboks.',
 'tog-previewonfirst' => 'Wys voorskou met eerste wysiging',
 'tog-nocache' => 'Deaktiveer blaaier se bladsykas',
-'tog-enotifwatchlistpages' => 'Stuur vir my e-pos met bladsyveranderings',
+'tog-enotifwatchlistpages' => "Stuur my e-pos as 'n bladsye of lêer op my dophoulys verander",
 'tog-enotifusertalkpages' => 'Stuur vir my e-pos as my eie besprekingsblad verander word',
-'tog-enotifminoredits' => 'Stuur ook e-pos vir klein bladsywysigings',
+'tog-enotifminoredits' => 'Stuur my ook e-pos vir klein wysigings aan bladsye en lêers',
 'tog-enotifrevealaddr' => 'Stel my e-posadres bloot in kennisgewingspos',
 'tog-shownumberswatching' => 'Wys die aantal gebruikers wat dophou',
 'tog-oldsig' => 'Bestaande handtekening:',
@@ -471,6 +471,10 @@ $1",
 'youhavenewmessages' => 'U het $1 (sien $2).',
 'newmessageslink' => 'nuwe boodskappe',
 'newmessagesdifflink' => 'die laaste wysiging',
+'youhavenewmessagesfromusers' => "U het $1 van {{PLURAL:$3|'n ander gebruiker|$3 gebruikers}} ($2).",
+'youhavenewmessagesmanyusers' => 'U het $1 van baie gebruikers ($2).',
+'newmessageslinkplural' => "{{PLURAL:$1|'n nuwe boodskap|nuwe boodskappe}}",
+'newmessagesdifflinkplural' => 'laaste {{PLURAL:$1|wysiging|wysigings}}',
 'youhavenewmessagesmulti' => 'U het nuwe boodskappe op $1',
 'editsection' => 'wysig',
 'editold' => 'wysig',
@@ -567,6 +571,8 @@ Indien dit nie die geval is nie, het u moontlik 'n fout in die sagteware ontdek.
 'cannotdelete' => 'Die bladsy of lêer "$1" kon nie skrap word nie.
 Iemand anders het dit moontlik reeds geskrap.',
 'cannotdelete-title' => 'Bladsy "$1" kan nie verwyder word nie',
+'delete-hook-aborted' => "Die wysiging is deur 'n hoek gekanselleer.
+Geen verduideliking is verskaf nie.",
 'badtitle' => 'Ongeldige titel',
 'badtitletext' => "Die bladsytitel waarvoor gevra is, is ongeldig, leeg, of
 'n verkeerd geskakelde tussen-taal of tussen-wiki titel.",
@@ -594,7 +600,7 @@ Probeer asseblief weer oor 'n paar minute.",
 'ns-specialprotected' => 'Spesiale bladsye kan nie geredigeer word nie.',
 'titleprotected' => "Hierdie titel is beskerm teen skepping deur [[User:$1|$1]].
 Die rede gegee is ''$2''.",
-'filereadonlyerror' => 'Dit was nie moontlik om die lêer "$1" te wysig nie lêerstoor tans lees-alleen is.
+'filereadonlyerror' => 'Dit was nie moontlik om die lêer "$1" te wysig nie omdat die lêerstoor "$2" tans lees-alleen is.
 
 Die rede hiervoor is "\'\'$3\'\'".',
 'invalidtitle-knownnamespace' => 'Ongeldige titel met naamruimte "$2" en teks "$3"',
@@ -621,6 +627,7 @@ moenie vergeet om u [[Special:Preferences|persoonlike voorkeure vir {{SITENAME}}
 'remembermypassword' => 'Onthou dat ek op hierdie rekenaar ingeteken het (vir \'n maksimum van $1 {{PLURAL:$|dag|dae}})',
 'securelogin-stick-https' => 'Bly verbind met HTTPS na aanmelding',
 'yourdomainname' => 'U domein:',
+'password-change-forbidden' => 'U kan nie wagwoorde op hierdie wiki verander nie.',
 'externaldberror' => "'n Databasis fout het voorgekom tydens aanmelding of u het nie toestemming om u eksterne rekening op te dateer nie.",
 'login' => 'Teken in',
 'nav-login-createaccount' => 'Teken in',
@@ -861,13 +868,17 @@ U kan [[Special:Search/{{PAGENAME}}|vir die bladsytitel in ander bladsye soek]],
 of [{{fullurl:{{FULLPAGENAME}}|action=edit}} hierdie bladsy wysig]</span>.',
 'noarticletext-nopermission' => 'Daar is tans geen teks in hierdie bladsy nie. U kan vir die bladsytitel [[Special:Search/{{PAGENAME}}|in ander bladsye soek]] of
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} die verwante logboeke deursoek]</span>.',
+'missing-revision' => 'Die weergawe #$1 van die bladsy "{{PAGENAME}} bestaan nie.
+
+Dit word meestal veroorsaak deur die volg van \'n verouderde verwysing na \'n bladsy wat verwyder is.
+Meer gegewens kan moontlik in die [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} skraplogboek] gevind word.',
 'userpage-userdoesnotexist' => 'U is besig om \'n gebruikersblad wat nie bestaan nie te wysig (gebruiker "<nowiki>$1</nowiki>"). Maak asseblief seker of u die bladsy wil skep/ wysig.',
 'userpage-userdoesnotexist-view' => 'Die gebruiker "$1" is nie geregistreer nie.',
 'blocked-notice-logextract' => 'Hierdie gebruiker is tans geblokkeer.
 Die laaste inskrywing in die blokkeerlogboek word hieronder vertoon:',
 'clearyourcache' => "'''Let wel''': Na die voorkeure gestoor is, moet u blaaier se kasgeheue verfris word om die veranderinge te sien:
-* '''Firefox / Safari:''' hou ''Shift'' en kliek ''Reload'', of druk ''Ctrl-F5'' of ''Ctrl-R'' (''Command-R'' op 'n Mac)
-* '''Google Chrome:''' druk ''Ctrl-Shift-R'' (''Command-Shift-R'' op 'n Mac)
+* '''Firefox / Safari:''' hou ''Shift'' en kliek ''Reload'', of druk ''Ctrl-F5'' of ''Ctrl-R'' (''-R'' op 'n Mac)
+* '''Google Chrome:''' druk ''Ctrl-Shift-R'' (''-Shift-R'' op 'n Mac)
 * '''Internet Explorer:''' hou ''Ctrl'' en kliek ''Refresh'', of druk ''Ctrl-F5''
 * '''Konqueror:''' kliek ''Reload'' of druk ''F5''
 * '''Opera:''' maak die kas skoon by ''Tools → Preferences''",
@@ -986,6 +997,7 @@ Hierdie parameters is uitgelaat.",
 'expansion-depth-exceeded-warning' => 'Die bladsy bevat te veel sjablone',
 'parser-unstrip-loop-warning' => '\'n "Unstrip"-lus is bespreur.',
 'parser-unstrip-recursion-limit' => 'Die rekursielimiet ($1) vir "unstrip" is oorskry',
+'converter-manual-rule-error' => "'n Fout is in 'n handmatig toegevoegde taalomskalelingsreël gevind.",
 
 # "Undo" feature
 'undo-success' => 'Die wysiging kan ongedaan gemaak word.
@@ -1042,15 +1054,13 @@ Vir meer besonderhede, raadpleeg die [{{fullurl:{{#Special:Log}}/delete|page={{F
 'rev-deleted-text-unhide' => "Hierdie weergawe van die bladsy is '''verwyder'''.
 Details kan in die [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} skraplogboek] gevind word.
 U kan steeds na [$1 die weergawe kyk] as u wil voortgaan.",
-'rev-suppressed-text-unhide' => "Hierdie weergawe van die blad word '''onderdruk'''.
-Details kan moontlik in die [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} logboek van onderdrukte weergawes] gesien word.
-As administrateur kan u, as u wil, na [$1 die verskille kyk].",
+'rev-suppressed-text-unhide' => "Hierdie weergawe van die bladsy word '''onderdruk'''.
+Details kan in die [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} logboek van onderdrukte weergawes] gevind word.
+As u wil voortgaan kan u steeds [$1 die weergawe sien].",
 'rev-deleted-text-view' => "Hierdie weergawe is '''verwyder'''.
-As administrateur kan u dit wel sien.
-Details kan moontlik in die [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} skraplogboek] aanwesig wees.",
+U dit wel sien. Details kan in die [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} skraplogboek] gevind word.",
 'rev-suppressed-text-view' => "Hierdie weergawe van die bladsy word '''onderdruk'''.
-As administrateur kan u dit sien.
-Details kan moontlik in die [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} logboek van onderdrukte weergawes] gesien word.",
+U kan dit wel sien. Details kan in die [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} logboek van onderdrukte weergawes] gevind word.",
 'rev-deleted-no-diff' => "U kan nie die verskille sien nie omdat een van die weergawes '''verwyder''' is.
 Details kan moontlik in die [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} skraplogboek] aanwesig wees.",
 'rev-suppressed-no-diff' => "U kan nie hierdie verskil sien nie omdat een van die weergawes '''geskrap''' is.",
@@ -1063,7 +1073,7 @@ As administrateur kan u steeds [$1 hierdie weergawe sien] as u wil voortgaan.",
 'rev-deleted-diff-view' => "Een van die weergawes van die verskil wat u aangevra het is '''verwyder'''.
 As administrateur kan u hierdie verskil sien. Meer details mag moontlik is die [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} verwyderingslogboek] beskikbaar wees.",
 'rev-suppressed-diff-view' => "Een van die weergawes vir die verskil wat u aangevra het is '''onderdruk'''.
-As administrateur kan u hierdie verskil sien. Meer details mag moontlik in die [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} verbergingslogboek] beskikbaar wees.",
+U kan wel hierdie verskil sien. Meer details kan in die [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} verbergingslogboek] gevind word.",
 'rev-delundel' => 'wys/versteek',
 'rev-showdeleted' => 'wys',
 'revisiondelete' => 'Verwyder/herstel weergawes',
@@ -1129,8 +1139,8 @@ Kontroleer asseblief die logboeke.",
 
 # Suppression log
 'suppressionlog' => 'Verbergingslogboek',
-'suppressionlogtext' => 'Die onderstaande lys bevat verwyderings en blokkades wat vir administrateurs verborge is.
-Kyk na die [[Special:IPBlockList|IP-blokkeerlys]] om die huidige blokkades te sien.',
+'suppressionlogtext' => 'Die onderstaande lys bevat verwyderings en blokkades wat vir administrateurs versteek is.
+Kyk na die [[Special:BlockList|blokkeerlys]] om die huidige blokkades te sien.',
 
 # History merging
 'mergehistory' => 'Geskiedenis van bladsy samesmeltings',
@@ -1390,7 +1400,7 @@ Die inligting is vir ander gebruikers sigbaar.',
 'userrights-no-interwiki' => 'U het nie toestemming om gebruikersregte op ander wikis te verander nie.',
 'userrights-nodatabase' => 'Databasis $1 bestaan nie of is nie hier beskikbaar nie.',
 'userrights-nologin' => "U moet [[Special:UserLogin|aanteken]] as 'n administrateur om gebruikersregte te mag toeken.",
-'userrights-notallowed' => 'U het nie die toestemming om gebruikersregte toe te ken nie.',
+'userrights-notallowed' => 'U het nie magtiging om gebruikersregte by te sit of weg te neem nie.',
 'userrights-changeable-col' => 'Groepe wat u kan verander',
 'userrights-unchangeable-col' => 'Groepe wat u nie kan verander nie',
 
@@ -1736,6 +1746,17 @@ As daar steeds probleme is, kontak 'n [[Special:ListUsers/sysop|administrateur]]
 'backend-fail-read' => 'Kon nie lêer $1 lees nie.',
 'backend-fail-create' => 'Kon nie lêer $1 uitskryf nie.',
 
+# Lock manager
+'lockmanager-notlocked' => 'Kon nie "$1" ontgrendel nie omdat dit nie gesluit is nie.',
+'lockmanager-fail-closelock' => 'Kon nie die slotlêer vir "$1" te sluit nie.',
+'lockmanager-fail-deletelock' => 'Kon nie die slotlêer vir "$1" skrap nie.',
+'lockmanager-fail-acquirelock' => 'Kon nie "$1" vergrendel nie.',
+'lockmanager-fail-openlock' => 'Kon nie die slotlêer vir "$1" oopmaak nie.',
+'lockmanager-fail-releaselock' => 'Kon nie "$1" ontgrendel nie.',
+'lockmanager-fail-db-release' => "Kon nie 'n vergrendeling op databasis $1 ophef nie.",
+'lockmanager-fail-svr-acquire' => "Kon nie 'n vergrendeling op bediener $1 verkry nie.",
+'lockmanager-fail-svr-release' => 'Kon nie vergrandeling op bediener $1 ophef nie.',
+
 # ZipDirectoryReader
 'zip-file-open-error' => "'n Fout het voorgekom met die opening van die lêer vir ZIP toetsing.",
 'zip-wrong-format' => "Die gespesifiseerde lêer was nie 'n zip-lêer nie.",
@@ -1759,7 +1780,7 @@ Dit kan nie behoorlik vir sekuriteit nagegaan word nie.",
 'img-auth-nopathinfo' => 'PATH_INFO word vermis.
 U bediener is nie ingestel om hierdie inligting deur te stuur nie.
 Miskien gebruik dit CGI, waartydens img_auth nie ondersteun word nie.
-[https://www.mediawiki.org/wiki/Manual:Image_Authorization Sien regte vir beelde] vir meer inligting.',
+Sien https://www.mediawiki.org/wiki/Manual:Image_Authorization vir meer inligting.',
 'img-auth-notindir' => 'Die aangevraagde pad is nie die ingestelde oplaaigids nie.',
 'img-auth-badtitle' => 'Dit was nie moontlik om \'n geldige bladsynaam van "$1" te maak nie.',
 'img-auth-nologinnWL' => 'U is nie aangeteken en "$1" is nie op die witlys nie.',
@@ -1929,8 +1950,8 @@ Die beskrywing op die [$2 lêer se inligtingsblad] word hieronder weergegee.',
 
 'disambiguations' => 'Bladsye wat na dubbelsinnigheidsbladsye skakel',
 'disambiguationspage' => 'Template:Dubbelsinnig',
-'disambiguations-text' => "Die volgende bladsye skakel na '''dubbelsinnigheidsbladsye'''.
-Die bladsye moet gewysig word om eerder direk na die regte onderwerpe te skakel.<br />
+'disambiguations-text' => "Die volgende bladsye het ten minste een skakel na 'n '''dubbelsinnigheidsbladsy'''.
+Hulle moet gewysig word om eerder direk na die regte onderwerpe te skakel.<br />
 'n Bladsy word beskou as 'n dubbelsinnigheidsbladsy as dit 'n sjabloon bevat wat geskakel is vanaf [[MediaWiki:Disambiguationspage]]",
 
 'doubleredirects' => 'Dubbele aansture',
@@ -2036,6 +2057,7 @@ Dit is dus moontlik dat 'n lêer hier gelys word terwyl dit tog in gebruik is.",
 U kan die resultate vernou deur 'n boekstaaftipe, gebruikersnaam (kas-sensitief) of spesifieke blad (ook kas-sensitief) te kies.",
 'logempty' => 'Geen inskrywings in die logboek voldoen aan die kriteria nie.',
 'log-title-wildcard' => 'Soek bladsye wat met die naam begin',
+'showhideselectedlogentries' => 'Wys/versteek gekose logboekreëls',
 
 # Special:AllPages
 'allpages' => 'Alle bladsye',
@@ -2080,8 +2102,9 @@ Sie ook [[Special:WantedCategories|nie-bestaande kategorieë met verwysings]].',
 'linksearch-pat' => 'Soekpatroon:',
 'linksearch-ns' => 'Naamruimte:',
 'linksearch-ok' => 'Soek',
-'linksearch-text' => 'Patrone soos "*.wikipedia.org" of "*.org" kan gebruik word.<br />
-Ondersteunde protokolle: <tt>$1</tt>',
+'linksearch-text' => 'Patrone soos "*.wikipedia.org" kan gebruik word.<br />
+Benodig ten minste een topvlakdomein, soos byvoorbeeld "*.org".<br />
+Ondersteunde protokolle: <tt>$1</tt> (moenie hierdie in u soektog gebruik nie)',
 'linksearch-line' => '$1 geskakel vanaf $2',
 'linksearch-error' => 'Patrone kan slegs aan die begin van die rekenaarnaam geplaas word.',
 
@@ -2273,6 +2296,8 @@ Tree asseblief versigtig op.",
 'rollback' => 'Rol veranderinge terug',
 'rollback_short' => 'Rol terug',
 'rollbacklink' => 'Rol terug',
+'rollbacklinkcount' => 'rol {{PLURAL:$1|een wysiging|$1 wysigings}} terug',
+'rollbacklinkcount-morethan' => 'rol meer as {{PLURAL:$1|een wysiging|$1 wysigings}} terug',
 'rollbackfailed' => 'Terugrol onsuksesvol',
 'cantrollback' => 'Kan nie na verandering terug keer nie; die laaste bydraer is die enigste outer van hierdie bladsy.',
 'alreadyrolled' => "Dit is nie moontlik om die laaste wysiging van die bladsy [[:$1]] deur [[User:$2|$2]] ([[User talk:$2|bespreking]]{{int:pipe-separator}}[[Special:Contributions/$2|bydraes]]) ongedaan te maak nie.
@@ -2501,8 +2526,8 @@ Vul 'n spesifieke rede hier onder in (haal byvoorbeeld spesifieke bladsye wat ge
 'ipb-confirm' => 'Bevestig blokkade',
 'badipaddress' => 'Die IP-adres is nie in die regte formaat nie.',
 'blockipsuccesssub' => 'Blokkering het geslaag',
-'blockipsuccesstext' => "[[Special:Contributions/$1|$1]] is geblokkeer.<br />
-Sien die [[Special:BlockList|IP-bloklys]] vir 'n oorsig van blokkerings.",
+'blockipsuccesstext' => '[[Special:Contributions/$1|$1]] is geblokkeer.<br />
+Sien die [[Special:BlockList|bloklys]] vir onlangse blokkades.',
 'ipb-blockingself' => 'U is besig om uself te blokkeer! Is u seker u wil dit doen?',
 'ipb-confirmhideuser' => 'U staan op die punt om \'n "versteekte gebruiker" te blokkeer. Dit sal die gebruiker se naam vanaf alle lyste en logboeke onderdruk. Is u seker u wil om dit te doen?',
 'ipb-edit-dropdown' => 'Werk lys van redes by',
@@ -2554,8 +2579,9 @@ Die blokkeerlogboek word hieronder ter verwysing weergegee:',
 Die verbergingslogboek word hieronder ter verwysing weergegee:',
 'blocklogentry' => '"[[$1]]" is vir \'n periode van $2 $3 geblok',
 'reblock-logentry' => 'het die instellings vir die blokkade vir [[$1]] gewysig. Dit verval nou op $2 om $3',
-'blocklogtext' => "Hier is 'n lys van onlangse blokkeer en deblokkeer aksies. Outomaties geblokkeerde IP-adresse word nie vertoon nie.
-Sien die [[Special:BlockList|IP-bloklys]] vir geblokkeerde adresse.",
+'blocklogtext' => "Hierdie is 'n lys van onlangse blokkeer- en deblokkeer-aksies.
+Outomaties geblokkeerde IP-adresse word nie vertoon nie.
+Sien die [[Special:BlockList|bloklys]] vir tans geblokkeerde adresse.",
 'unblocklogentry' => 'blokkade van $1 is opgehef:',
 'block-log-flags-anononly' => 'anonieme gebruikers alleenlik',
 'block-log-flags-nocreate' => 'registrasie van gebruikers geblokkeer',
@@ -2822,6 +2848,7 @@ Die lêer is slegs gedeeltelik opgelaai.',
 'javascripttest-pagetext-frameworks' => 'Kies een van die volgende toetsraamwerke: $1',
 'javascripttest-pagetext-skins' => "Kies 'n omslag waarmee die toets uitgevoer moet word:",
 'javascripttest-qunit-intro' => 'Sien die [$1 toetsdokumentasie] op mediawiki.org.',
+'javascripttest-qunit-heading' => 'QUnit toetssuite vir MediaWiki JavaScript',
 
 # Tooltip help for the actions
 'tooltip-pt-userpage' => 'U gebruikerbladsy',
@@ -3783,6 +3810,8 @@ Beelde word in hulle volle resolusie gewys. Ander lêertipes word direk met hull
 'api-error-empty-file' => 'Die lêer wat u probeer oplaai is leeg.',
 'api-error-emptypage' => 'Die skep van leë nuwe bladsye word nie toegelaat nie.',
 'api-error-fetchfileerror' => 'Interne fout: Iets het verkeerd gegaan met die haal van die lêer.',
+'api-error-fileexists-forbidden' => 'Daar is reeds \'n lêer met die naam "$1" wat nie oorskryf kan word nie.',
+'api-error-fileexists-shared-forbidden' => 'Daar is reeds \'n lêer met die naam "$1" in die gedeelde lêerstoor, en kan nie oorskryf word nie.',
 'api-error-file-too-large' => 'Die lêer wat u probeer oplaai is te groot.',
 'api-error-filename-tooshort' => 'Die lêernaam is te kort.',
 'api-error-filetype-banned' => 'Hierdie tipe lêer is verban en word nie toegelaat nie.',
@@ -3822,5 +3851,5 @@ Beelde word in hulle volle resolusie gewys. Ander lêertipes word direk met hull
 'duration-millennia' => '$1 {{PLURAL:$1|millennium|millennia}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => 'Die {{PLURAL:$4|lêertipe|lêertipes}} word $1 nie toegelaat nie. Toelaatbare {{PLURAL:$3|lêertipes|lêertipes}} is $2.',
+'api-error-filetype-banned-type' => 'Die {{PLURAL:$4|lêertipe|lêertipes}} $1 word nie toegelaat nie. Toelaatbare {{PLURAL:$3|lêertipes|lêertipes}} is $2.',
 );
index 4fee78c..1e84470 100644 (file)
@@ -603,7 +603,7 @@ $messages = array(
 # Cologne Blue skin
 'qbfind' => 'جد',
 'qbbrowse' => 'تصفح',
-'qbedit' => 'عدل',
+'qbedit' => 'تعدÙ\8aل',
 'qbpageoptions' => 'هذه الصفحة',
 'qbpageinfo' => 'سياق النص',
 'qbmyoptions' => 'صفحاتي',
@@ -613,14 +613,14 @@ $messages = array(
 
 # Vector skin
 'vector-action-addsection' => 'أضف موضوعا',
-'vector-action-delete' => 'احذÙ\81',
+'vector-action-delete' => 'حذف',
 'vector-action-move' => 'انقل',
 'vector-action-protect' => 'احم',
 'vector-action-undelete' => 'استرجع الحذف',
 'vector-action-unprotect' => 'غير الحماية',
 'vector-simplesearch-preference' => 'مكّن مقترحات البحث المُحسّنة (لواجهة فكتور فقط)',
 'vector-view-create' => 'أنشئ',
-'vector-view-edit' => 'عدل',
+'vector-view-edit' => 'تعدÙ\8aل',
 'vector-view-history' => 'اعرض التاريخ',
 'vector-view-view' => 'اقرأ',
 'vector-view-viewsource' => 'اعرض المصدر',
@@ -633,7 +633,7 @@ $messages = array(
 'tagline' => 'من {{SITENAME}}',
 'help' => 'مساعدة',
 'search' => 'بحث',
-'searchbutton' => 'ابحث',
+'searchbutton' => 'بحث',
 'go' => 'اذهب',
 'searcharticle' => 'اذهب',
 'history' => 'تاريخ الصفحة',
@@ -643,11 +643,11 @@ $messages = array(
 'permalink' => 'وصلة دائمة',
 'print' => 'اطبع',
 'view' => 'عرض',
-'edit' => 'عدل',
+'edit' => 'تعدÙ\8aل',
 'create' => 'أنشئ',
-'editthispage' => 'عدل هذه الصفحة',
+'editthispage' => 'تعدÙ\8aل هذه الصفحة',
 'create-this-page' => 'أنشئ هذه الصفحة',
-'delete' => 'احذÙ\81',
+'delete' => 'حذف',
 'deletethispage' => 'احذف هذه الصفحة',
 'undelete_short' => 'استرجاع {{PLURAL:$1|تعديل واحد|تعديلين|$1 تعديلات|$1 تعديل|$1 تعديلا}}',
 'viewdeleted_short' => 'عرض {{PLURAL:$1|تعديل محذوف|$1 تعديلات محذوفة}}',
@@ -682,7 +682,7 @@ $messages = array(
 'protectedpage' => 'صفحة محمية',
 'jumpto' => 'اذهب إلى:',
 'jumptonavigation' => 'تصفح',
-'jumptosearch' => 'اÙ\84بحث',
+'jumptosearch' => 'بحث',
 'view-pool-error' => 'عذرا، الخوادم منهكة حاليا.
 يحاول مستخدمون كثر الوصول إلى هذه الصفحة.
 من فضلك انتظر قليلا قبل أن تحاول الوصول إلى هذه الصفحة مجددا.
@@ -724,11 +724,15 @@ $1',
 'youhavenewmessages' => 'توجد لديك $1 ($2).',
 'newmessageslink' => 'رسائل جديدة',
 'newmessagesdifflink' => 'آخر تغيير',
+'youhavenewmessagesfromusers' => 'لديك $1 من {{PLURAL:$3|مستخدم آخر|$3 مستخدمين}} ($2).',
+'youhavenewmessagesmanyusers' => 'لديك $1 من مستخدمين كثر ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|رسالة جديدة|رسائل جديدة}}',
+'newmessagesdifflinkplural' => 'آخر {{PLURAL:$1|تغيير|تغييرات}}',
 'youhavenewmessagesmulti' => 'لديك رسائل جديدة على $1',
-'editsection' => 'عدل',
-'editold' => 'عدل',
+'editsection' => 'تعدÙ\8aل',
+'editold' => 'تعدÙ\8aل',
 'viewsourceold' => 'اعرض المصدر',
-'editlink' => 'عدل',
+'editlink' => 'تعدÙ\8aل',
 'viewsourcelink' => 'اعرض المصدر',
 'editsectionhint' => 'حرر القسم: $1',
 'toc' => 'محتويات',
@@ -879,6 +883,7 @@ $2',
 'remembermypassword' => 'تذكر دخولي بهذا المتصفح (لمدة أقصاها {{PLURAL:$1||يوم واحد|يومان|$1 أيام|$1 يوما|$1 يوم}})',
 'securelogin-stick-https' => 'ابقَ في اتصال HTTPS بعد الدخول.',
 'yourdomainname' => 'نطاقك:',
+'password-change-forbidden' => 'أنت لا يمكنك تغيير كلمات السر على هذا الويكي.',
 'externaldberror' => 'هناك إما خطأ في دخول قاعدة البيانات الخارجية أو أنه غير مسموح لك بتحديث حسابك الخارجي.',
 'login' => 'ادخل',
 'nav-login-createaccount' => 'ادخل / أنشئ حسابا',
@@ -1060,10 +1065,10 @@ $2
 'minoredit' => 'هذا تعديل طفيف',
 'watchthis' => 'راقب هذه الصفحة',
 'savearticle' => 'احفظ الصفحة',
-'preview' => 'معاينة',
+'preview' => 'عرض مسبق',
 'showpreview' => 'أظهر معاينة',
 'showlivepreview' => 'عرض مباشر',
-'showdiff' => 'أظÙ\87ر التغييرات',
+'showdiff' => 'عرض التغييرات',
 'anoneditwarning' => "'''تحذير:''' لم تقم بالدخول.
 سيسجل عنوان الآيبي خاصتك في تاريخ هذه الصفحة.",
 'anonpreviewwarning' => "''أنت غير مسجل الدخول. الحفظ سيسجل عنوان الأيبي الخاص بك في تاريخ هذه الصفحة.''",
@@ -1130,6 +1135,10 @@ $2
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} البحث في السجلات المتعلقة]،
 أو [{{fullurl:{{FULLPAGENAME}}|action=edit}} تعديل هذه الصفحة]</span>.',
 'noarticletext-nopermission' => 'لا يوجد حاليا أي نص في هذه الصفحة.يمكنك [[Special:Search/{{PAGENAME}}|البحث عن عنوان هذه الصفحة]] في الصفحات الأخرى,أو <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} بحث السجلات المتصلة]</span>.',
+'missing-revision' => 'المراجعة #$1 من الصفحة المسماة "{{PAGENAME}}" غير موجودة.
+
+هذا يحدث عادة عن طريق اتباع وصلة تاريخ قديمة لصفحة تم حذفها.
+التفاصيل يمكن إيجادها في [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سجل الحذف].',
 'userpage-userdoesnotexist' => 'حساب المستخدم "<nowiki>$1</nowiki>" غير مسجل.
 من فضلك تأكد أنك تريد إنشاء/تعديل هذه الصفحة.',
 'userpage-userdoesnotexist-view' => 'حساب المستخدم "$1" غير مسجل.',
@@ -1263,6 +1272,7 @@ $2
 'expansion-depth-exceeded-warning' => 'الصفحة تجاوزت عمق التوسيع',
 'parser-unstrip-loop-warning' => 'حلقة معراة تم الكشف عنها',
 'parser-unstrip-recursion-limit' => 'تعدى حد العودية Unstrip  ($1)',
+'converter-manual-rule-error' => 'خطأ تم اكتشافه في قاعدة تحويل اللغة اليدوية',
 
 # "Undo" feature
 'undo-success' => 'يمكن استرجاع التعديل.
@@ -1450,9 +1460,13 @@ $1",
 'lineno' => 'سطر $1:',
 'compareselectedversions' => 'قارن بين النسختين المختارتين',
 'showhideselectedversions' => 'أظهر/أخف المراجعات المختارة',
-'editundo' => 'تراجع',
+'editundo' => 'رجÙ\88ع',
 'diff-multi' => '({{PLURAL:$1||مراجعة واحدة متوسطة غير معروضة أجراها|مراجعتان متوسطتان غير معروضتين أجراهما|$1 مراجعات متوسطة غير معروضة أجراها|$1 مراجعة متوسطة غير معروضة أجراها}} {{PLURAL:$2||مستخدم واحد|مستخدمان|$2 مستخدمين|$2 مستخدمًا|$2 مستخدم}}.)',
 'diff-multi-manyusers' => '({{PLURAL:$1||مراجعة واحدة متوسطة غير معروضة أجراها|مراجعتان متوسطتان غير معروضتان أجراهما|$1 مراجعات متوسطة غير معروضة أجراها|$1 مراجعة متوسطة غير معروضة أجراها}} أكثر من {{PLURAL:$2||مستخدم واحد|مستخدمين|$2 مستخدمين|$2 مستخدمًا|$2 مستخدم}}.)',
+'difference-missing-revision' => '{{PLURAL:$2|مراجعة واحدة|$2 مراجعات}} لهذا الفرق ($1) {{PLURAL:$2|لم|لم}} يتم إيجادها.
+
+هذا يحدث عادة عن طريق اتباع وصلة فرق قديمة لصفحة تم حذفها.
+التفاصيل يمكن إيجادها في [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سجل الحذف].',
 
 # Search results
 'searchresults' => 'نتائج البحث',
@@ -1534,11 +1548,11 @@ $1",
 'preferences' => 'تفضيلات',
 'mypreferences' => 'تفضيلاتي',
 'prefs-edits' => 'عدد التعديلات:',
-'prefsnologin' => 'غير مسجل',
+'prefsnologin' => 'غير مسجل الدخول',
 'prefsnologintext' => 'يجب أن تكون <span class="plainlinks">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}} مسجل الدخول]</span>  حتى تتمكن من تعديل تفضيلات المستخدم.',
 'changepassword' => 'غير كلمة السر',
 'prefs-skin' => 'واجهة',
-'skin-preview' => 'عاÙ\8aÙ\86',
+'skin-preview' => 'عرض Ù\85سبÙ\82',
 'datedefault' => 'لا تفضيل',
 'prefs-beta' => 'مزايا بيتا',
 'prefs-datetime' => 'وقت وتاريخ',
@@ -1565,7 +1579,7 @@ $1",
 'prefs-edit-boxsize' => 'حجم نافذة التحرير.',
 'rows' => 'صفوف:',
 'columns' => 'أعمدة:',
-'searchresultshead' => 'ابحث',
+'searchresultshead' => 'بحث',
 'resultsperpage' => 'عدد النتائج في الصفحة:',
 'stub-threshold' => 'الحد لتنسيق <a href="#" class="stub">وصلة البذرة</a>:',
 'stub-threshold-disabled' => 'معطل',
@@ -1855,7 +1869,7 @@ $1",
 'uploadbtn' => 'ارفع الملف',
 'reuploaddesc' => 'إلغاء الرفع والرجوع إلى استمارة الرفع',
 'upload-tryagain' => 'أرسل وصف ملف معدل',
-'uploadnologin' => 'لم تقم بتسجيل الدخول',
+'uploadnologin' => 'غير مسجل الدخول',
 'uploadnologintext' => 'يجب أن تكون [[Special:UserLogin|مسجلا الدخول]] لتتمكن من رفع الملفات.',
 'upload_directory_missing' => 'مجلد الرفع ($1) مفقود ولم يمكن إنشاؤه بواسطة خادوم الوب.',
 'upload_directory_read_only' => 'مجلد الرفع ($1) لا يمكن الكتابة عليه بواسطة خادوم الوب.',
@@ -2181,7 +2195,7 @@ $1',
 'filedelete-intro' => "أنت على وشك حذف الملف '''[[Media:$1|$1]]''' مع كل تاريخه.",
 'filedelete-intro-old' => "أنت تحذف نسخة '''[[Media:$1|$1]]''' بتاريخ [$4 $3، $2].",
 'filedelete-comment' => 'السبب:',
-'filedelete-submit' => 'احذÙ\81',
+'filedelete-submit' => 'حذف',
 'filedelete-success' => "'''$1''' تم حذفه.",
 'filedelete-success-old' => "نسخة '''[[Media:$1|$1]]''' بتاريخ $3، $2 تم حذفها.",
 'filedelete-nofile' => "'''$1''' غير موجود.",
@@ -2245,8 +2259,8 @@ $1',
 
 'disambiguations' => 'الصفحات التي ترتبط بصفحات توضيح',
 'disambiguationspage' => 'Template:توضيح',
-'disambiguations-text' => "اÙ\84صÙ\81حات Ø§Ù\84تاÙ\84Ù\8aØ© ØªØµل إلى '''صفحة توضيح'''.
-ينبغي في المقابل أن تصل إلى الصفحة الملائمة. <br />
+'disambiguations-text' => "اÙ\84صÙ\81حات Ø§Ù\84تاÙ\84Ù\8aØ© ØªØ­ØªÙ\88Ù\8a Ø¹Ù\84Ù\89 Ù\88صÙ\84Ø© Ù\88احدة Ø¹Ù\84Ù\89 Ø§Ù\84Ø£Ù\82ل إلى '''صفحة توضيح'''.
+ربما ينبغي أن تصل إلى صفحة أكثر ملائمة. <br />
 تعامل الصفحة كصفحة توضيح إذا كان بها قالب موجود في [[MediaWiki:Disambiguationspage]]",
 
 'doubleredirects' => 'تحويلات مزدوجة',
@@ -2259,7 +2273,7 @@ $1',
 
 'brokenredirects' => 'تحويلات مكسورة',
 'brokenredirectstext' => 'التحويلات التالية تصل لصفحات غير موجودة:',
-'brokenredirects-edit' => 'عدل',
+'brokenredirects-edit' => 'تعدÙ\8aل',
 'brokenredirects-delete' => 'حذف',
 
 'withoutinterwiki' => 'صفحات بدون وصلات لغات أخرى',
@@ -2398,7 +2412,7 @@ $1',
 'linksearch' => 'بحث في الوصلات الخارجية',
 'linksearch-pat' => 'نمط البحث:',
 'linksearch-ns' => 'النطاق:',
-'linksearch-ok' => 'ابحث',
+'linksearch-ok' => 'بحث',
 'linksearch-text' => 'Wildcards مثل "*.wikipedia.org" يمكن استخدامها.
 تحتاج على الأقل إلى نطاق ذو مستوى أعلى، كمثال "*.org".<br />
 البروتوكولات المدعومة: <tt>$1</tt> (لا تقم بإضافة أي من هذه إلى بحثك).',
@@ -2461,7 +2475,7 @@ $1',
 'emailnotarget' => 'المستلم ليس مستخدمًا موجودًا أو ليس مستخدمًا صحيحًا.',
 'emailtarget' => 'أدخل اسم مستخدم المستلم',
 'emailusername' => 'اسم المستخدم:',
-'emailusernamesubmit' => 'أرسل',
+'emailusernamesubmit' => 'إرسال',
 'email-legend' => 'إرسال بريد إلكتروني إلى مستخدم {{SITENAME}} آخر',
 'emailfrom' => 'من:',
 'emailto' => 'إلى:',
@@ -2484,7 +2498,7 @@ $1',
 'watchlistfor2' => 'ل$1 $2',
 'nowatchlist' => 'لا توجد مدخلات في قائمة مراقبتك.',
 'watchlistanontext' => 'الرجاء $1 لعرض أو تعديل الصفحات في قائمة مراقبتك.',
-'watchnologin' => 'غير مسجل',
+'watchnologin' => 'غير مسجل الدخول',
 'watchnologintext' => 'يجب أن تكون [[Special:UserLogin|مسجل الدخول]] لتعدل قائمة مراقبتك.',
 'addwatch' => 'إضافة إلى قائمة المراقبة',
 'addedwatchtext' => "أضيفت الصفحة  \"[[:\$1]]\" إلى [[Special:Watchlist|قائمة مراقبتك]].
@@ -2560,7 +2574,7 @@ $UNWATCHURL
 'exblank' => 'الصفحة كانت فارغة',
 'delete-confirm' => 'حذف "$1"',
 'delete-legend' => 'حذف',
-'historywarning' => "'''تحذير:''' الصفحة التي توشك على حذفها لها تاريخ فيه {{PLURAL:$1||مراجعة واحدة|مراجعتان|$1 مراجعات|$1 مراجعة}} تقريباً:",
+'historywarning' => "'''تحذير:''' الصفحة التي توشك على حذفها لها تاريخ فيه {{PLURAL:$1||مراجعة واحدة|مراجعتان|$1 مراجعات|$1 مراجعة}} تقريبا:",
 'confirmdeletetext' => 'أنت على وشك أن تقوم بحذف صفحة بالإضافة إلى كل تاريخها.
 من فضلك التأكد من عزمك على الحذف، وبأنك مدرك للعواقب، وبأنك تقوم بهذا بالتوافق مع [[{{MediaWiki:Policy-url}}|السياسة]].',
 'actioncomplete' => 'انتهاء العملية',
@@ -2589,6 +2603,8 @@ $UNWATCHURL
 'rollback' => 'استرجاع التعديلات',
 'rollback_short' => 'استرجع',
 'rollbacklink' => 'استرجع',
+'rollbacklinkcount' => 'استرجع {{PLURAL:$1|لا تعديلات|تعديلاً واحداً|تعديلين|$1 تعديلات|$1 تعديل}}',
+'rollbacklinkcount-morethan' => 'استرجاع أكثر من $1 {{PLURAL:$1|تعديل|تعديلات}}',
 'rollbackfailed' => 'لم ينجح الاسترجاع',
 'cantrollback' => 'لم يمكن استرجاع التعديل؛
 آخر مساهم هو المؤلف الوحيد لهذه الصفحة.',
@@ -2665,7 +2681,7 @@ $UNWATCHURL
 'pagesize' => '(بايت)',
 
 # Restrictions (nouns)
-'restriction-edit' => 'اÙ\84تعدÙ\8aÙ\84',
+'restriction-edit' => 'تعديل',
 'restriction-move' => 'النقل',
 'restriction-create' => 'الإنشاء',
 'restriction-upload' => 'الرفع',
@@ -2715,7 +2731,7 @@ $UNWATCHURL
 'undelete-search-title' => 'البحث في الصفحات المحذوفة',
 'undelete-search-box' => 'ابحث في الصفحات المحذوفة',
 'undelete-search-prefix' => 'عرض الصفحات التي تبدأ بـ:',
-'undelete-search-submit' => 'ابحث',
+'undelete-search-submit' => 'بحث',
 'undelete-no-results' => 'لم يتم العثور على صفحات مطابقة في أرشيف المحذوفات.',
 'undelete-filename-mismatch' => 'لم يمكن استرجاع مراجعة الملف بتاريخ $1: اسم الملف لا يطابق',
 'undelete-bad-store-key' => 'لم يمكن استرجاع مراجعة الملف بتاريخ $1: الملف كان مفقوداً قبل الحذف',
@@ -2764,7 +2780,7 @@ $1',
 'sp-contributions-search' => 'بحث عن مساهمات',
 'sp-contributions-username' => 'عنوان أيبي أو اسم مستخدم:',
 'sp-contributions-toponly' => 'أظهر أعلى المراجعات فقط',
-'sp-contributions-submit' => 'ابحث',
+'sp-contributions-submit' => 'بحث',
 
 # What links here
 'whatlinkshere' => 'ماذا يصل هنا',
@@ -2849,7 +2865,7 @@ $1',
 'blocklist-by' => 'حظر المشرف',
 'blocklist-params' => 'معطيات المنع',
 'blocklist-reason' => 'السبب',
-'ipblocklist-submit' => 'ابحث',
+'ipblocklist-submit' => 'بحث',
 'ipblocklist-localblock' => 'المنع المحلي',
 'ipblocklist-otherblocks' => '{{PLURAL:$1||المنع الآخر|المنعان الآخران|المنوعات الأخرى}}',
 'infiniteblock' => 'لا نهائي',
@@ -2971,7 +2987,7 @@ $1',
 وفي هذه الحالات، يجب عليك نقل أو دمج محتويات الصفحة يدويا، إذا رغب في ذلك.",
 'movearticle' => 'انقل الصفحة:',
 'moveuserpage-warning' => "'''تحذير: أنت على وشك نقل صفحة مستخدم. من فضلك لاحظ أن الصفحة وحدها سوف تنقل وأن المستخدم لن يعاد تسميته.'''",
-'movenologin' => 'غير مسجل',
+'movenologin' => 'غير مسجل الدخول',
 'movenologintext' => 'يجب أن تكون مستخدماً مسجلاً وأن  [[Special:UserLogin|تسجل دخولك]] لكي تنقل صفحة.',
 'movenotallowed' => 'أنت لا تمتلك الصلاحية لنقل الصفحات.',
 'movenotallowedfile' => 'أنت لا تمتلك الصلاحية لنقل الملفات.',
@@ -3364,15 +3380,16 @@ $1',
 'newimages-label' => 'اسم الملف (أو جزء منه):',
 'showhidebots' => '($1 بوتات)',
 'noimages' => 'لا شيء للعرض.',
-'ilsubmit' => 'ابحث',
+'ilsubmit' => 'بحث',
 'bydate' => 'حسب التاريخ',
 'sp-newimages-showfrom' => 'أظهر الملفات الجديدة بدءا من $2، $1',
 
 # Video information, used by Language::formatTimePeriod() to format lengths in the above messages
 'video-dims' => '$1، $2×$3',
-'seconds-abbrev' => '$1s',
-'minutes-abbrev' => '$1m',
-'hours-abbrev' => '$1h',
+'seconds-abbrev' => '$1ث',
+'minutes-abbrev' => '$1ق',
+'hours-abbrev' => '$1س',
+'days-abbrev' => '$1ي',
 'seconds' => '{{PLURAL:$1||ثانية واحدة|ثانيتين|$1 ثوانٍ|$1 ثانية}}',
 'minutes' => '{{PLURAL:$1||دقيقة واحدة|دقيقتين|$1 دقائق|$1 دقيقة}}',
 'hours' => '{{PLURAL:$1||ساعة واحدة|ساعتين|$1 ساعات|$1 ساعة}}',
@@ -3596,6 +3613,7 @@ $1',
 'exif-compression-2' => 'CCITT المجموعة 3 -1 تعديل طول تشغيل ترميز هوفمان البعدي',
 'exif-compression-3' => 'ترميز فاكس المجموعة 3 CCITT',
 'exif-compression-4' => 'ترميز فاكس المجموعة 4 CCITT',
+'exif-compression-5' => 'إل زد دبليو',
 'exif-compression-6' => 'JPEG (قديم)',
 'exif-compression-7' => 'جيه بي إي جي',
 'exif-compression-8' => 'Deflate (أدوبي)',
@@ -3965,9 +3983,25 @@ $5
 'size-kilobytes' => '$1 كيلوبايت',
 'size-megabytes' => '$1 ميجابايت',
 'size-gigabytes' => '$1 جيجابايت',
+'size-terabytes' => '$1 تيرابايت',
+'size-petabytes' => '$1 بيتابايت',
+'size-exabytes' => '$1 إكسابايت',
+'size-zetabytes' => '$1 زيتابايت',
+'size-yottabytes' => '$1 يوتابايت',
+
+# Bitrate units
+'bitrate-bits' => '$1بيت لكل ثانية',
+'bitrate-kilobits' => '$1كيلوبيت لكل ثانية',
+'bitrate-megabits' => '$1ميجابيت لكل ثانية',
+'bitrate-gigabits' => '$1جيجابيت لكل ثانية',
+'bitrate-terabits' => '$1تيرابيت لكل ثانية',
+'bitrate-petabits' => '$1بيتابيت لكل ثانية',
+'bitrate-exabits' => '$1إكسابيت لكل ثانية',
+'bitrate-zetabits' => '$1زيتابيت لكل ثانية',
+'bitrate-yottabits' => '$1يوتابيت لكل ثانية',
 
 # Live preview
-'livepreview-loading' => 'يُحمّل...',
+'livepreview-loading' => 'جاري التحميل...',
 'livepreview-ready' => 'يُحمّل… جاهز!',
 'livepreview-failed' => 'فشل العرض المباشر!
 حاول تجربة العرض العادي.',
@@ -4078,6 +4112,7 @@ $5
 'version-variables' => 'المتغيرات',
 'version-antispam' => 'منع البريد المزعج',
 'version-skins' => 'واجهات',
+'version-api' => 'إيه بي آي',
 'version-other' => 'أخرى',
 'version-mediahandlers' => 'متحكمات الميديا',
 'version-hooks' => 'الخطاطيف',
@@ -4102,6 +4137,8 @@ $5
 'version-entrypoints' => 'نقطة دخول روابط المواقع',
 'version-entrypoints-header-entrypoint' => 'تقطة دخول',
 'version-entrypoints-header-url' => 'المسار',
+'version-entrypoints-articlepath' => '[https://www.mediawiki.org/wiki/Manual:$wgArticlePath مسار المقالات]',
+'version-entrypoints-scriptpath' => '[https://www.mediawiki.org/wiki/Manual:$wgScriptPath مسار السكريبت]',
 
 # Special:FilePath
 'filepath' => 'مسار ملف',
@@ -4115,7 +4152,7 @@ $5
 'fileduplicatesearch-summary' => 'ابحث عن الملفات المكررة بناء على قيم الهاش.',
 'fileduplicatesearch-legend' => 'بحث عن مكرر',
 'fileduplicatesearch-filename' => 'اسم الملف:',
-'fileduplicatesearch-submit' => 'ابحث',
+'fileduplicatesearch-submit' => 'بحث',
 'fileduplicatesearch-info' => '$1 × $2 بكسل<br />حجم الملف: $3<br />نوع MIME: $4',
 'fileduplicatesearch-result-1' => 'الملف "$1" ليس له تكرار مطابق.',
 'fileduplicatesearch-result-n' => 'الملف "$1" له {{PLURAL:$2|1 تكرار مطابق|$2 تكرار مطابق}}.',
@@ -4164,7 +4201,7 @@ $5
 'tags-display-header' => 'الظهور في قوائم التغييرات',
 'tags-description-header' => 'وصف كامل للمعنى',
 'tags-hitcount-header' => 'تغييرات موسومة',
-'tags-edit' => 'عدل',
+'tags-edit' => 'تعدÙ\8aل',
 'tags-hitcount' => '{{PLURAL:$1|لا تغييرات|تغيير واحد|تغييران|$1 تغييرات|$1 تغييرا|$1 تغيير}}',
 
 # Special:ComparePages
@@ -4196,7 +4233,7 @@ $5
 'htmlform-int-toolow' => 'القيمة التي حددتها أقل من الحد الأدنى وهو $1',
 'htmlform-int-toohigh' => 'القيمة التي حددتها أكبر من الحد الأقصى وهو $1',
 'htmlform-required' => 'هذه القيمة مطلوبة',
-'htmlform-submit' => 'أرسل',
+'htmlform-submit' => 'إرسال',
 'htmlform-reset' => 'الرجوع عن التغييرات',
 'htmlform-selectorother-other' => 'أخرى',
 
@@ -4304,5 +4341,5 @@ $5
 'duration-millennia' => '{{PLURAL: $1||ألفية واحدة|ألفيتان|$1 ألفيات|$1 ألفية}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|ليس نوع ملف مسموح به|ليسوا أنواع ملفات مسموح بها}}. {{PLURAL:$3|نوع الملف المسموح به هو|أنواع الملفات المسموح بها هي}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|ليس نوع ملف مسموح به|ليست أنواع ملفات مسموح بها}}. {{PLURAL:$3|نوع الملف المسموح به هو|أنواع الملفات المسموح بها هي}} $2.',
 );
index 18d7c8b..2984e10 100644 (file)
@@ -433,8 +433,13 @@ $1',
 'resetpass-temp-password' => 'ܡܠܬܐ ܕܥܠܠܐ ܙܒܢܢܝܬܐ:',
 
 # Special:PasswordReset
+'passwordreset' => 'ܣܘܡ ܡܠܬܐ ܕܥܠܠܐ ܙܒܢ ܐܚܪܝܢ',
+'passwordreset-legend' => 'ܣܘܡ ܡܠܬܐ ܕܥܠܠܐ ܙܒܢ ܐܚܪܝܢ',
 'passwordreset-username' => 'ܫܡܐ ܕܡܦܠܚܢܐ:',
 
+# Special:ChangeEmail
+'changeemail' => 'ܫܚܠܦ ܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
+
 # Edit page toolbar
 'bold_sample' => 'ܟܬܒܬܐ ܥܒܝܬܐ',
 'bold_tip' => 'ܟܬܒܬܐ ܥܒܝܬܐ',
@@ -445,6 +450,7 @@ $1',
 'extlink_tip' => 'ܐܣܘܪܐ ܒܪܝܐ (ܕܟܘܪ http:// ܩܕܡܝܬܐ)',
 'headline_sample' => 'ܨܚܚܐ ܕܦܪܫܓܢܐ ܪܫܝܐ',
 'nowiki_sample' => 'ܣܢܘܦ ܟܬܒܬܐ ܕܠܐ ܣܕܝܪܘܬܐ ܗܪܟܐ',
+'nowiki_tip' => 'ܒܣܝ ܣܕܝܪܘܬܐ ܕܘܝܩܝ',
 'image_tip' => 'ܠܦܦܐ ܛܡܝܪܐ',
 'media_tip' => 'ܐܣܘܪܐ ܕܠܦܦܐ',
 'sig_tip' => 'ܪܡܝ ܐܝܕܟ ܥܡ ܙܒܢܐ ܘܣܝܩܘܡܐ',
@@ -979,10 +985,11 @@ $1',
 'unusedimages' => 'ܠܦܦ̈ܐ ܠܐ ܦܠܝܚ̈ܐ',
 'popularpages' => 'ܦܐܬܬ̈ܐ ܡܫܡܗܬ̈ܐ',
 'wantedcategories' => 'ܣܕܪ̈ܐ ܒܥܝ̈ܐ',
-'wantedpages' => 'ܦÜ\90ܬܬÌ\88Ü\90 Ü\92Ü¥Ü\9dܬ̈ܐ',
-'wantedfiles' => 'ܠܦܦÌ\88Ü\90 Ü\92Ü¥Ü\9d̈ܐ',
-'wantedtemplates' => 'Ü©Ü Ü\92Ì\88Ü\90 Ü\92Ü¥Ü\9d̈ܐ',
+'wantedpages' => 'ܦÜ\90ܬܬÌ\88Ü\90 Ü£Ü¢Ü\9dÜ©ܬ̈ܐ',
+'wantedfiles' => 'ܠܦܦÌ\88Ü\90 Ü£Ü¢Ü\9dÜ©̈ܐ',
+'wantedtemplates' => 'Ü©Ü Ü\92Ì\88Ü\90 Ü£Ü¢Ü\9dÜ©̈ܐ',
 'prefixindex' => 'ܟܠ ܦܐܬܬ̈ܐ ܥܡ ܫܪܘܝܐ',
+'prefixindex-namespace' => 'ܟܠ ܦܐܬܬ̈ܐ ܥܡ ܫܪܘܝܐ ($1 ܚܩܠܐ)',
 'shortpages' => 'ܦܐܬܬ̈ܐ ܟܪ̈ܝܬܐ',
 'longpages' => 'ܦܐܬܬ̈ܐ ܐܪ̈ܝܟܬܐ',
 'deadendpages' => 'ܦܐܬܬ̈ܐ ܥܡ ܚܪܬܐ ܡܝܬܬܐ',
@@ -1294,6 +1301,7 @@ $1',
 'unblockip' => 'ܫܩܘܠ ܚܪܡܐ ܡܢ ܡܦܠܚܢܐ',
 'ipusubmit' => 'ܫܩܘܠ ܚܪܡܐ ܗܢܐ',
 'unblocked' => 'ܐܫܬܩܠ ܚܪܡܐ ܡܢ [[User:$1|$1]]',
+'blocklist' => 'ܡܦܠܚܢ̈ܐ ܡܚܪ̈ܡܐ',
 'ipblocklist' => 'ܡܦܠܚܢ̈ܐ ܡܚܪ̈ܡܐ',
 'ipblocklist-legend' => 'ܐܫܟܚ ܡܦܠܚܢܐ ܡܚܪܡܐ',
 'ipblocklist-submit' => 'ܒܨܝ',
index 6605bfc..0d3a49f 100644 (file)
@@ -454,6 +454,10 @@ $2',
 'filereadonlyerror' => "«$1» файлын үҙгәртеп булмай, сөнки «$2» һаҡлағысы «уҡыу өсөн генә» тәртибендә.
 
 Был сикләүҙе индергән хаким биргән аңлатма:«''$3''».",
+'invalidtitle-knownnamespace' => '"$2" исем арауығы һәм "$3"  тексты исем өсөн ярамай',
+'invalidtitle-unknownnamespace' => '"$2" тексты һәм "$1" арауыҡ өсөн билдәһеҙ номерлы исем ярамай',
+'exception-nologin' => 'Танылмағанһығыҙ',
+'exception-nologin-text' => 'Был битте ҡарар йәки һоратылған ғәмәлде башҡарыр өсөн системала танылыу кәрәк.',
 
 # Virus scanner
 'virus-badscanner' => "Көйләү хатаһы: Билдәһеҙ вирустар сканеры: ''$1''",
@@ -474,6 +478,7 @@ $2',
 'remembermypassword' => 'Был компьютерҙа паролемде иҫләргә ($1 {{PLURAL:$1|көндән|көндән}} күп түгел)',
 'securelogin-stick-https' => 'Танылғандан һуң HTTPS менән бәйләнеште ҡалдырырға',
 'yourdomainname' => 'Һеҙҙең домен',
+'password-change-forbidden' => 'Был викила паролегеҙҙе үҙгәртә алмайһығыҙ.',
 'externaldberror' => 'Тышҡы мәғлүмәт базаһы менән танылғанда хата барлыҡҡа килде йәки тышҡы үҙ көйләүҙәрегеҙҙе үҙгәртер өсөн хоҡуҡтарығыҙ етәрле түгел.',
 'login' => 'Танышыу йәки теркәлеү',
 'nav-login-createaccount' => 'Танышыу йәки теркәлеү',
@@ -709,6 +714,10 @@ $2
 'noarticletext-nopermission' => 'Хәҙерге ваҡытта был биттә текст юҡ.
 Һеҙ башҡа мәҡәләләрҙә [[Special:Search/{{PAGENAME}}|был исемде]] йәки
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} журналдағы яҙмаларҙы] эҙләй алаһығыҙ.</span>',
+'missing-revision' => '"{{PAGENAME}}" исемле биттең $1 номерлы өлгөһө юҡ.
+
+Был хәл, ғәҙәттә, юйылған биткә яһалған һылтанманын ваҡыты үтеүенән барлыҡҡа килә.
+Тулыраҡ мәғлүмәт өсөн ҡарағыҙ: [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} юйыу яҙмалары].',
 'userpage-userdoesnotexist' => '«<nowiki>$1</nowiki>» иҫәп яҙыуы юҡ. Һеҙ бындай бит яһарға йәки битте үҙгәртергә теләһәгеҙ яңынан тикшерегеҙ.',
 'userpage-userdoesnotexist-view' => '«$1» исемле иҫәп яҙыуы юҡ.',
 'blocked-notice-logextract' => 'Хәҙергә был ҡатнашыусы ябылған. Һуңғы ҡулланыусы ябыу яҙмаһы:',
@@ -826,6 +835,13 @@ $2
 'parser-template-loop-warning' => 'Төйөн табылған ҡалыптар: [[$1]]',
 'parser-template-recursion-depth-warning' => '($1) ҡалыбын рекурсия итеп ҡулланыу тәрәнлеге рөхсәт ителгәндән артып киткән',
 'language-converter-depth-warning' => 'Телдәрҙе үҙгәртеү тәрәнлегенең сиге үткән ($1)',
+'node-count-exceeded-category' => 'Төйөндәр һаны артҡан биттәр',
+'node-count-exceeded-warning' => 'Биттә төйөндәр һаны артып киткән',
+'expansion-depth-exceeded-category' => 'Асылыу тәрәнлеге артып киткән биттәр',
+'expansion-depth-exceeded-warning' => 'Биттә һалыныу тәрәнлеге сиге үтеп кителгән',
+'parser-unstrip-loop-warning' => 'Ябылмаған pre табылды',
+'parser-unstrip-recursion-limit' => '($1) рекурсия сиге үтеп кителгән',
+'converter-manual-rule-error' => 'Тел әйлендереү ҡағиҙәһендә хата табылды',
 
 # "Undo" feature
 'undo-success' => 'Был үҙгәртеүҙе кире алып була. Зинһар, улар һеҙҙе ҡыҙыҡһындырған үҙгәртеүҙәр булыуынан шикләнмәҫ өсөн версияларҙы сағыштырыуҙы ҡарағыҙ һәм үҙгәртеүҙәрҙе ғәмәлғә керетер өсөн «Битте һаҡларға» төймәһенә баҫығыҙ.',
@@ -1002,6 +1018,8 @@ $1",
 
 # Diffs
 'history-title' => '$1 битенең үҙгәртеү тарихы',
+'difference-title' => '$1 — версиялар араһындағы айырма',
+'difference-title-multipage' => '«$1» һәм «$2» биттәре араһындағы айырма',
 'difference-multipage' => '(Биттәр араһындағы айырма)',
 'lineno' => '$1 юл:',
 'compareselectedversions' => 'Һайланған версияларҙы сағыштырыу',
@@ -1009,6 +1027,10 @@ $1",
 'editundo' => 'кире алыу',
 'diff-multi' => '({{PLURAL:$2|$2 ҡатнашыусының}} {{PLURAL:$1|ваҡытлы версияһы}} күрһәтелмәгән)',
 'diff-multi-manyusers' => '(Кәмендә {{PLURAL:$2|$2 ҡатнашыусының}} {{PLURAL:$1|ваҡытлы версияһы}} күрһәтелмәгән)',
+'difference-missing-revision' => '$1 айырмаһының {{PLURAL:$2|бер өлгөһө|$2 өлгөһө}} табылманы.
+
+Был хәл, ғәҙәттә, юйылған биткә яһалған айырма һылтанмаһының ваҡыты үтеүенән барлыҡҡа килә.
+Тулыраҡ мәғлүмәт өсөн ҡарағыҙ: [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} юйыу яҙмалары].',
 
 # Search results
 'searchresults' => 'Эҙләү һөҙөмтәләре',
@@ -1279,6 +1301,7 @@ $1",
 'right-writeapi' => 'Яҙҙырыу өсөн API ҡулланыу',
 'right-delete' => 'Биттәрҙе юйырға',
 'right-bigdelete' => 'Тарихы оҙон булған биттәрҙе юйыу',
+'right-deletelogentry' => 'Журналдың билдәле яҙмаларын юйыу һәм тергеҙеү.',
 'right-deleterevision' => 'Биттәрҙең күрһәтелгән өлгөләрен юйыу һәм тергеҙеү',
 'right-deletedhistory' => 'Биттәрҙең юйылған тарих яҙмаларын текстһыҙ ҡарау',
 'right-deletedtext' => 'Биттең юйылған өлгөләре араһындағы юйылған текстты һәм үҙгәртеүҙәрҙе ҡарау',
@@ -1561,11 +1584,14 @@ $1',
 'backend-fail-closetemp' => 'Ваҡытлы файлды ябып булмай.',
 'backend-fail-read' => '«$1» файлын уҡып булмай.',
 'backend-fail-create' => '«$1» файлын яҙып булмай.',
+'backend-fail-maxsize' => '$1 файлын яҙып булманы, сөнки уның күләме {{PLURAL:$2|$2 байттан|$2 байттан}} күп.',
 'backend-fail-readonly' => '$1 һаҡлағысы әлегә уҡыу өсөн генә асыҡ. Сәбәбе: $2',
 'backend-fail-synced' => '$1 файлы эске һаҡлағыста ярашһыҙ хәлдә тора.',
 'backend-fail-connect' => '"$1" һаҡлағысы менән бәйләнеш яһап булманы.',
 'backend-fail-internal' => '$1 һаҡлағысында билдәһеҙ хата килеп сыҡты',
 'backend-fail-contenttype' => 'Файлды $1 адресына һаҡлар өсөн уның эстәлеге төрөн билдәләп булманы.',
+'backend-fail-batchsize' => 'Һаҡлағыс $1 {{PLURAL:$1|файл операцияһынан|файл операцияһынан}} бер блок алды, сикләү һаны: $2 {{PLURAL:$1|операция|операция}}.',
+'backend-fail-usable' => 'Хоҡуҡтар етмәгәнлектән йәки кәрәкле папкалар булмағанлыҡтан $1 файлын яҙып булманы.',
 
 # File journal errors
 'filejournal-fail-dbconnect' => '"$1" мәғлүмәт базаһы журналына тоташып булманы.',
@@ -1580,6 +1606,7 @@ $1',
 'lockmanager-fail-releaselock' => '"$1" асҡысының биген асып булманы.',
 'lockmanager-fail-db-bucket' => '$1 сегментында етәрле күләмдә бикләү базаһы менән бәйләнеп булманы.',
 'lockmanager-fail-db-release' => '$1 мәғлүмәттәр базаһы биген сисеп булманы.',
+'lockmanager-fail-svr-acquire' => '$1 серверындағы биктәрҙе алып булманы.',
 'lockmanager-fail-svr-release' => '$1 серверы биктәрен сисеп булманы.',
 
 # ZipDirectoryReader
@@ -1691,6 +1718,10 @@ $1',
 Тулыраҡ мәғлүмәтте [$2 файл тасуирламаһы битендә] ҡарарға мөмкин.',
 'sharedupload-desc-here' => 'Был файл $1 базаһынан һәм башҡа проекттарҙа ҡулланыла ала.
 [$2 файл тасуирламаһы битенән] тулыраҡ мәғлүмәт түбәндә килтерелгән.',
+'sharedupload-desc-edit' => 'Файл килгән урын: $1. Был файл башҡа сайттарҙа ҡулланылырға мөмкин.
+Тасуиламаһын [$2 кәрәкле биттә] үҙгәртергә була.',
+'sharedupload-desc-create' => 'Файл килгән урын: $1. Был файл башҡа сайттарҙа ҡулланылырға мөмкин.
+Тасуиламаһын [$2 кәрәкле биттә] үҙгәртергә була.',
 'filepage-nofile' => 'Бындай исемле файл юҡ.',
 'filepage-nofile-link' => 'Бындай исемле файл юҡ. Һеҙ уны [$1 тейәй алаһығыҙ].',
 'uploadnewversion-linktext' => 'Был файлдың яңы версияһын тейәргә',
@@ -1777,7 +1808,7 @@ $1',
 
 'disambiguations' => 'Күп мәғәнәле төшөнсәләр биттәренә һылтанған биттәр',
 'disambiguationspage' => 'Template:Күп_мәғәнәлелек',
-'disambiguations-text' => "ТүбÓ\99ндÓ\99ге Ð±Ð¸Ñ\82Ñ\82Ó\99Ñ\80Ò\99Ó\99н '''күп Ð¼Ó\99Ò\93Ó\99нÓ\99ле Ð±Ð¸Ñ\82Ñ\82Ó\99Ñ\80гÓ\99''' Ò»Ñ\8bлÑ\82анма Ñ\8fһалÒ\93ан.
+'disambiguations-text' => "Ð\9aилÓ\99һе Ð±Ð¸Ñ\82Ñ\82Ó\99Ñ\80Ò\99Ó\99 ÐºÓ\99мендÓ\99 Ð±ÐµÑ\80 '''күп Ð¼Ó\99Ò\93Ó\99нÓ\99ле Ð±Ð¸Ñ\82Ñ\82Ó\99Ñ\80гÓ\99''' Ò»Ñ\8bлÑ\82анма Ð±Ð°Ñ\80.
 Бының урынына улар фәҡәт үҙенә кәрәкле мәҡәләгә һылтанырға тейеш.<br />
 Әгәр биттә исеме [[MediaWiki:Disambiguationspage]] битендә күрһәтелгән ҡалып ҡулланылһа, ул күп мәғәнәле тип иҫәпләнә.",
 
@@ -1825,6 +1856,8 @@ $1',
 'wantedpages' => 'Кәрәкле биттәр',
 'wantedpages-badtitle' => 'Һорау һөҙөмтәләрендә дөрөҫ булмаған исем: $1',
 'wantedfiles' => 'Кәрәкле файлдар',
+'wantedfiletext-cat' => 'Киләһе файлдарҙы улар булмаған хәлдә ҡулланырға тырышыла. Тыш һаҡлағыстарҙа булған файлдар был исемлеккә яңылыш эләгеүе мөмкин. Бындай хаталы белдереүҙәр <del>һыҙыҡ</del> менән күрһәтеләсәк. Шулай уҡ, булмаған файлдарҙы алған биттәр киләһе исемлектә күрһәтелгән: [[:$1]]',
+'wantedfiletext-nocat' => 'Киләһе файлдарҙы улар булмаған хәлдә ҡулланырға тырышыла. Тыш һаҡлағыстарҙа булған файлдар был исемлеккә яңылыш эләгеүе мөмкин. Бындай хаталы белдереүҙәр <del>һыҙыҡ</del> менән күрһәтеләсәк.',
 'wantedtemplates' => 'Кәрәкле ҡалыптар',
 'mostlinked' => 'Иң күп һылтанма яһалған биттәр',
 'mostlinkedcategories' => 'Иң күп һылтанма яһалған категориялар',
@@ -1883,6 +1916,7 @@ $1',
 'alllogstext' => '{{SITENAME}} проектының дөйөм яҙмалар журналы исемлеге. Һеҙ һөҙөмтәләрҙе журнал төрө буйынса, ҡатаншыусы исеме буйынса (ҙур/бәләкәй хәрефкә һиҙгер) йәки ҡағылған бит исеме буйынса (шулай уҡ ҙур/бәләкәй хәрефкә һиҙгер) һайлап ала алаһығыҙ.',
 'logempty' => 'Журнал яҙмаларында һайланған юлдар юҡ.',
 'log-title-wildcard' => 'Керетелгән хәрефтәр менән башланған исемдәрҙе табырға',
+'showhideselectedlogentries' => 'Журналдың һайланған яҙмаларын күрһәтергә/йәшерергә.',
 
 # Special:AllPages
 'allpages' => 'Бөтә биттәр',
@@ -1904,6 +1938,8 @@ $1',
 'allpages-hide-redirects' => 'Йүнәлтеүҙәрҙе йәшерергә',
 
 # SpecialCachedPage
+'cachedspecial-viewing-cached-ttl' => 'Һеҙ биттең кэшланған өлгөһөн ҡарайһығыҙ. Уның $1 элек яңыртылыуы мөмкин.',
+'cachedspecial-viewing-cached-ts' => 'Һеҙ биттең кэшланған өлгөһөн ҡарайһығыҙ. Уның хәҙерге өлгөнән бик ныҡ айырылыуы мөмкин.',
 'cachedspecial-refresh-now' => 'Һуңғы версияны ҡарарға',
 
 # Special:Categories
@@ -2117,6 +2153,8 @@ $UNWATCHURL
 'rollback' => 'Үҙгәртеүҙәрҙе кире ҡайтарырға',
 'rollback_short' => 'Кире ҡайтарырға',
 'rollbacklink' => 'кире алырға',
+'rollbacklinkcount' => '$1 {{PLURAL:$1|төҙәтеүҙе|төҙәтеүҙе}} кире алырға',
+'rollbacklinkcount-morethan' => '$1 {{PLURAL:$1|төҙәтеүҙән|төҙәтеүҙән}} күберәк кире алырға',
 'rollbackfailed' => 'Кире ҡайтарырғанда барлыҡҡа килгән хата',
 'cantrollback' => 'Үҙгәртеүҙәрҙе кире алыу мөмкин түгел. Битте һуңғы үҙгәртеүсе ҡатнашыусы уның берҙән-бер авторы булып тора.',
 'alreadyrolled' => '[[User:$2|$2]] ([[User talk:$2|фекер алышыу]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])  кереткән [[:$1]] һуңғы үҙгәртеүҙәрҙе кире алыу мөмкин түгел; башҡа ҡатнашыусы был битте мөхәррирләгән йәки үҙгәртеүҙәрҙе кире алған инде.
@@ -2767,6 +2805,7 @@ The wiki server cannot provide data in a format your client can read.',
 'spambot_username' => 'Спамдан таҙартыусы',
 'spam_reverting' => '$1 һылтанмаһыҙ һуңғы өлгөгә ҡайтарыу',
 'spam_blanking' => 'Бөтә өлгөләрҙә лә $1 һылтанмаһы бар, таҙартыу',
+'spam_deleting' => 'Бөтә өлгөләрҙә лә $1 һылтанма бар, таҙартыу бара',
 
 # Info page
 'pageinfo-title' => '«$1» буйынса мәғлүмәт',
@@ -3640,6 +3679,8 @@ MediaWiki файҙалы булыр, тигән өмөттә, ләкин БЕР
 'api-error-empty-file' => 'Һеҙ ебәргән файл буш.',
 'api-error-emptypage' => 'Яңы буш биттәр яһау тыйыла.',
 'api-error-fetchfileerror' => 'Эске хата: файлды күсергән ваҡытта хата китте',
+'api-error-fileexists-forbidden' => '«$1» исемле файл бар һәм өҫтөнә яҙып булмай.',
+'api-error-fileexists-shared-forbidden' => '«$1» исемле файл уртаҡ файлдар һаҡлағысында бар һәм өҫтөнә яҙып булмай.',
 'api-error-file-too-large' => 'Һеҙ ебәргән файл үтә ҙур.',
 'api-error-filename-tooshort' => 'Файл исеме бик ҡыҫҡа.',
 'api-error-filetype-banned' => 'Был файл төрө тыйылған.',
@@ -3679,5 +3720,5 @@ MediaWiki файҙалы булыр, тигән өмөттә, ләкин БЕР
 'duration-millennia' => '$1 {{PLURAL:$1|меңйыллыҡ|меңйыллыҡтар}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 — {{PLURAL:$4|тыйылған файл төрө|тыйылған файл төрҙәре}}. {{PLURAL:$3|Файлдың рөхсәт ителгән төрө|Файлдың рөхсәт ителгән төрҙәре:}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|тыйылған файл төрө|тыйылған файл төрҙәре}}. Рөхсәт ителгән {{PLURAL:$3|файл төрө|файл төрҙәре}} $2.',
 );
index ba505c6..df96d51 100644 (file)
@@ -1186,8 +1186,12 @@ Ini dae tabi matitingkog.',
 'prefs-help-realname' => 'Opsyonal an totoong pangaran asin kun itatao mo ini, gagamiton ini yangarig an mga sinurat mo maatribuir saimo.',
 'prefs-help-email' => 'Opsyonal an e-koreo, alagad pwede ka na masosog kan iba sa paagi kan saimong pahina o pahina nin olay na dai kinakaipuhan na ipabisto an identidad mo.',
 'prefs-help-email-required' => 'Kaipuhan an e-koreo.',
+'prefs-displaysearchoptions' => 'Ipahiling ang mga pagpipilian',
+'prefs-displaywatchlist' => 'Ipahiling ang mga pagpipilian',
+'prefs-diffs' => 'Diffs',
 
 # User preference: e-mail validation using jQuery
+'email-address-validity-valid' => 'An e-koreo nagpapahiling na balido',
 'email-address-validity-invalid' => 'Magkaag nin sarong balidong e-koreong address',
 
 # User rights
@@ -1261,6 +1265,7 @@ Ini dae tabi matitingkog.',
 'right-writeapi' => 'Gamit kan pagsurat sa API',
 'right-delete' => 'Puraon an mga pahina',
 'right-bigdelete' => 'Puraon an mga pahina na igwang darakulang mga historiya',
+'right-deletelogentry' => 'Puraon asin dae pagpuran an espesipikong mga entrada sa log',
 'right-deleterevision' => 'Puraon asin dae puraon an espisipikong pagbabago kan mga pahina',
 'right-deletedhistory' => 'Tanawon an pinagpurang mga entradang historiya, na dae kan saindang asosyadong teksto',
 'right-deletedtext' => 'Tanawon an pinagpurang teksto asin mga karibay sa tahaw kan mga pagbabagong pinagpura na',
@@ -1454,6 +1459,10 @@ An pinagtutugutan na {{PLURAL:$3|tipo nin sagunson|mga tipo nin mga sagunson}} $
 'windows-nonascii-filename' => 'Ining wiki dae tabi nagsusuporta kan mga pangaran kan sagunson na igwang espesyal na mga karakter.',
 'fileexists' => "Igwa nang ''file'' na may parehong pangaran sa ini, sosogon tabî an '''<tt>[[:$1]]</tt>''' kun dai ka seguradong ribayan ini.
 [[$1|thumb]]",
+'filepageexists' => "An pahinang pandeskripsyon kaining sagunson pinagmukna na tabi sa '''<tt>[[:$1]]</tt>''', alagad mayong sagunson na igwa kaining pangaran sa ngunyan nag-eeksister.
+An sumaryong na saimong ipinaglaog dae minaluwas sa pahina kan deskription.
+Tanganing gibohon na an saimong sumaryo magluwas duman, kaipohan mong manwal na pagliliwat kaini.
+[[$1|thumb]]",
 'fileexists-extension' => "May ''file'' na may parehong pangaran: [[$2|thumb]]
 * Pangaran kan pigkakargang ''file'': '''<tt>[[:$1]]</tt>'''
 * Pangaran kan yaon nang ''file'': '''<tt>[[:$2]]</tt>'''
index 846fdf5..9d33c64 100644 (file)
@@ -517,9 +517,13 @@ $1',
 'ok' => 'Добра',
 'pagetitle' => '$1 — {{SITENAME}}',
 'retrievedfrom' => 'Атрымана з «$1»',
-'youhavenewmessages' => 'Ð\92Ñ\8b Ð¼Ð°ÐµÑ\86е $1 ($2).',
+'youhavenewmessages' => 'Ð\92Ñ\8b Ð°Ñ\82Ñ\80Ñ\8bмалÑ\96 $1 ($2).',
 'newmessageslink' => 'новыя паведамленьні',
 'newmessagesdifflink' => 'апошняя зьмена',
+'youhavenewmessagesfromusers' => 'Вы атрымалі $1 ад {{PLURAL:$3|іншага ўдзельніка|$3 удзельнікаў}} ($2).',
+'youhavenewmessagesmanyusers' => 'Вы атрымалі $1 ад некалькіх удзельнікаў ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|новае паведамленьне|новыя паведамленьні}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|апошняя зьмена|апошнія зьмены}}',
 'youhavenewmessagesmulti' => 'Вы атрымалі новыя паведамленьні на $1',
 'editsection' => 'рэдагаваць',
 'editold' => 'рэдагаваць',
@@ -1041,6 +1045,7 @@ $2
 'expansion-depth-exceeded-warning' => 'Старонка перавысіла дазволеную глыбіню ўключэньняў',
 'parser-unstrip-loop-warning' => 'Вызначаная незачыненая пятля',
 'parser-unstrip-recursion-limit' => 'Перавышанае абмежаваньне глыбіні рэкурсіі ($1)',
+'converter-manual-rule-error' => 'Знойдзеная памылка ў ручным правіле моўнага канвэртара',
 
 # "Undo" feature
 'undo-success' => 'Рэдагаваньне можа быць адмененае. Калі ласка, параўнайце адрозьненьні паміж вэрсіямі, каб упэўніцца, што гэта адпаведныя зьмены, а потым запішыце зьмены для сканчэньня рэдагаваньня.',
index ce55402..a306928 100644 (file)
@@ -8,6 +8,7 @@
  * @file
  *
  * @author Aleator
+ * @author Arnaugir
  * @author Avm99963
  * @author BroOk
  * @author Cedric31
@@ -458,6 +459,10 @@ $1",
 'youhavenewmessages' => 'Tens $1 ($2).',
 'newmessageslink' => 'nous missatges',
 'newmessagesdifflink' => 'últims canvis',
+'youhavenewmessagesfromusers' => "Tens $1 {{PLURAL:$3|d'un altre usuari|de $3 usuaris}} ($2).",
+'youhavenewmessagesmanyusers' => 'Tens $1 de molts usuaris ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|un nou missatge|nous missatges}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|darrer canvi|darrers canvis}}',
 'youhavenewmessagesmulti' => 'Teniu nous missatges a $1',
 'editsection' => 'modifica',
 'editold' => 'modifica',
@@ -840,6 +845,10 @@ Podeu [[Special:Search/{{PAGENAME}}|cercar aquest títol]] en altres pàgines,
 o [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear-la ara]</span>.',
 'noarticletext-nopermission' => 'Actualment no hi ha text en aquesta pàgina.
 Podeu [[Special:Search/{{PAGENAME}}|cercar aquest títol]] en altres pàgines o bé <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cercar en els registres relacionats]</span>.',
+'missing-revision' => 'La revisió # $1  de la pàgina anomenada "{{PAGENAME}}" no existeix.
+
+Això és normalment provocat per després d\'un enllaç d\'història antiquat a una pàgina que s\'ha suprimit.
+Detalls es poden trobar en el [{{fullurl: {{# especial: registre}} / delete|page = {{FULLPAGENAMEE}}}} registre de supressió].',
 'userpage-userdoesnotexist' => "Atenció: El compte d'usuari «<nowiki>$1</nowiki>» no està registrat. En principi no hauríeu de crear ni editar aquesta pàgina.",
 'userpage-userdoesnotexist-view' => 'El compte d\'usuari "$1" no està registrat.',
 'blocked-notice-logextract' => "En aquests moments aquest compte d'usuari es troba blocat.
@@ -966,6 +975,7 @@ Se n'han omès els arguments.",
 'expansion-depth-exceeded-warning' => "La pàgina ha excedit la profunditat d'expansió",
 'parser-unstrip-loop-warning' => "S'ha detectat un bucle no desmuntable",
 'parser-unstrip-recursion-limit' => "S'ha excedit el límit ($1) de recursivitat no desmuntable",
+'converter-manual-rule-error' => 'Error detectat a la norma de conversió de llengua manual',
 
 # "Undo" feature
 'undo-success' => "Pot desfer-se la modificació. Si us plau, reviseu la comparació de sota per a assegurar-vos que és el que voleu fer; llavors deseu els canvis per a finalitzar la desfeta de l'edició.",
index 1f2e4b6..8a5287c 100644 (file)
@@ -148,7 +148,7 @@ $messages = array(
 # User preference toggles
 'tog-underline' => 'ھێڵ ھێنان بەژێر بەستەرەکان:',
 'tog-justify' => 'پەرەگرافەکان پڕاوپر نیشان بدە',
-'tog-hideminor' => 'دەستکارییە بچوکەکان بشارەوە لە دوا گۆڕانکارییەکاندا',
+'tog-hideminor' => 'دەستکارییە بچووکەکان بشارەوە لە دوایین گۆڕانکارییەکاندا',
 'tog-hidepatrolled' => 'لە دوایین گۆڕانکاریەکان، دەستکاریە پارێزراوەکان داشارە',
 'tog-newpageshidepatrolled' => 'لە لیستی لاپەڕە نوێکان، لاپەڕە پارێزراوەکان داشارە',
 'tog-extendwatchlist' => 'لیستی چاودێری درێژبکەرەوە بۆ نیشان دانی ھەموو گۆڕانکارییەکان، نەک تەنھا دوایینەکان.',
index 46efaca..44b9eb0 100644 (file)
@@ -7,6 +7,7 @@
  * @ingroup Language
  * @file
  *
+ * @author Chmee2
  * @author Danny B.
  * @author Dontlietome7
  * @author Helix84
@@ -620,6 +621,10 @@ $1',
 'youhavenewmessages' => 'Máte $1 ($2).',
 'newmessageslink' => 'nové zprávy',
 'newmessagesdifflink' => 'rozdíl oproti předchozí verzi',
+'youhavenewmessagesfromusers' => 'Máte $1 od {{PLURAL:$3|jiného uživatele|$3 jiných uživatelů}} ($2).',
+'youhavenewmessagesmanyusers' => 'Máte $1 od mnoha dalších uživatelů ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|novou zprávu|nové zprávy}}',
+'newmessagesdifflinkplural' => 'poslední {{PLURAL:$1|změna|změny}}',
 'youhavenewmessagesmulti' => 'Na $1 máte nové zprávy',
 'editsection' => 'editovat',
 'editold' => 'editovat',
index 3c4bc49..e752dd8 100644 (file)
@@ -380,6 +380,10 @@ $1',
 'youhavenewmessages' => 'Mae gennych chi $1 ($2).',
 'newmessageslink' => 'Neges(eueon) newydd',
 'newmessagesdifflink' => 'y newid diweddaraf',
+'youhavenewmessagesfromusers' => 'Mae gennych $1 oddi wrth {{PLURAL:$3||ddefnyddiwr arall|$2 ddefnyddiwr arall|$3 defnyddiwr arall|$3 defnyddiwr arall|$3 defnyddiwr arall}} ($2).',
+'youhavenewmessagesmanyusers' => 'Mae gennych $1 oddi wrth lu o ddefnyddwyr eraill ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|neges newydd|neges newydd|negeseuon newydd}}',
+'newmessagesdifflinkplural' => 'y {{PLURAL:$1||newid diweddaraf|newidiadau diweddaraf}}',
 'youhavenewmessagesmulti' => 'Mae negeseuon newydd gennych ar $1',
 'editsection' => 'golygu',
 'editold' => 'golygu',
@@ -529,6 +533,7 @@ Cofiwch osod y [[Special:Preferences|dewisiadau]] sydd fwyaf hwylus i chi ar {{S
 'remembermypassword' => "Y porwr hwn i gofio'r manylion mewngofnodi (am hyd at $1 {{PLURAL:$1||diwrnod|ddiwrnod|diwrnod|diwrnod|diwrnod}})",
 'securelogin-stick-https' => "Cadw'r cyswllt â HTTPS ar ôl mewngofnodi",
 'yourdomainname' => 'Eich parth',
+'password-change-forbidden' => 'Ni allwch newid cyfrineiriau ar y wici hwn.',
 'externaldberror' => "Naill ai: cafwyd gwall dilysu allanol ar databas neu: ar y llaw arall efallai nad oes hawl gennych chi i ddiwygio'ch cyfrif allanol.",
 'login' => 'Mewngofnodi',
 'nav-login-createaccount' => 'Mewngofnodi',
@@ -758,6 +763,10 @@ Os daethoch yma ar ddamwain, cliciwch botwm '''n&ocirc;l''' y porwr.",
 Gallwch [[Special:Search/{{PAGENAME}}|chwilio am y teitl hwn]] ar dudalennau eraill, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chwilio drwy'r logiau perthnasol], neu [{{fullurl:{{FULLPAGENAME}}|action=edit}} golygu'r dudalen]</span>.",
 'noarticletext-nopermission' => 'Mae\'r dudalen hon yn wag ar hyn o bryd.
 Gallwch [[Special:Search/{{PAGENAME}}|chwilio am y teitl hwn]] ar dudalennau eraill, neu gallwch <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chwilio drwy\'r logiau perthnasol]</span>.',
+'missing-revision' => 'Nid yw\'r diwygiad #$1 o\'r dudalen "{{PAGENAME}}" ar gael.
+
+Fel arfer, fe ddigwydd hyn wrth ddilyn hen gyswllt i dudalen sydd wedi ei dileu.
+Gallwch weld y manylion yn y [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} lòg dileu].',
 'userpage-userdoesnotexist' => 'Nid oes defnyddiwr a\'r enw "<nowiki>$1</nowiki>" yn bod. Gwnewch yn siwr eich bod am greu/golygu\'r dudalen hon.',
 'userpage-userdoesnotexist-view' => 'Nid yw\'r cyfrif defnyddiwr "$1" wedi ei gofrestri.',
 'blocked-notice-logextract' => "Mae'r defnyddiwr hwn wedi ei flocio ar hyn o bryd.
@@ -882,6 +891,7 @@ Cafodd yr argiau hyn eu hepgor.",
 'expansion-depth-exceeded-warning' => "Mae dyfnder ehangu'r dudalen y tu hwnt i'r terfyn",
 'parser-unstrip-loop-warning' => 'Wedi darganfod dolen dad-blicio (unstrip loop)',
 'parser-unstrip-recursion-limit' => 'Wedi mynd dros ben y terfyn ar ddychweliad dad-blicio (unstrip recursion) ($1)',
+'converter-manual-rule-error' => 'Cafwyd hyd i wall yn y rheol trosi iaith â llaw',
 
 # "Undo" feature
 'undo-success' => "Gellir dadwneud y golygiad. Byddwch gystal â gwirio'r gymhariaeth isod i sicrhau mai dyma sydd arnoch eisiau gwneud, ac yna rhowch y newidiadau ar gadw i gwblhau'r gwaith o ddadwneud y golygiad.",
@@ -1335,6 +1345,7 @@ Mae'r wybodaeth hon ar gael i'r cyhoedd.",
 'right-writeapi' => "Defnyddio'r API i ysgrifennu a thrin y tudalennau",
 'right-delete' => 'Dileu tudalennau',
 'right-bigdelete' => 'Dileu tudalennau a hanes llwythog iddynt',
+'right-deletelogentry' => 'Dileu a dad-ddileu cofnodion lòg penodedig',
 'right-deleterevision' => 'Dileu a dad-ddileu golygiadau arbennig o dudalennau',
 'right-deletedhistory' => 'Gweld cofnodion fersiynau sydd wedi eu dileu, heb y testun ynddynt',
 'right-deletedtext' => 'Gweld ysgrifen sydd wedi ei ddileu a newidiadau rhwng fersiynau ar ôl eu dileu',
@@ -1836,7 +1847,7 @@ Cofiwch chwilio am gysylltiadau eraill at nodyn a'u hystyried cyn ei ddileu.",
 
 'disambiguations' => "Tudalennau sy'n cysylltu â thudalennau gwahaniaethu",
 'disambiguationspage' => 'Template:Gwahaniaethu',
-'disambiguations-text' => "Mae'r tudalennau canlynol yn cynnwys un neu ragor o gysylltau wici, sydd yn cysylltu â '''thudalennau gwahaniaethu'''. Yn hytrach dylent arwain yn syth at yr erthygl briodol.<br />
+'disambiguations-text' => "Mae'r tudalennau canlynol yn cynnwys un neu ragor o gysylltau, sydd yn arwain at '''dudalennau gwahaniaethu'''. Hwyrach y byddai'n hwylusach petai'r cyswllt yn arwain yn syth at y dudalen briodol.<br />
 Diffinir tudalen yn dudalen gwahaniaethu pan mae'n cynnwys un o'r nodiadau '[[MediaWiki:Disambiguationspage|tudalen gwahaniaethu]]'.",
 
 'doubleredirects' => 'Ailgyfeiriadau dwbl',
@@ -2181,6 +2192,8 @@ Gallai dileu tudalen, gyda hanes golygu cymaint â hyn iddi, beri dryswch i weit
 'rollback' => 'Gwrthdroi golygiadau',
 'rollback_short' => 'Gwrthdroi',
 'rollbacklink' => 'gwrthdroi',
+'rollbacklinkcount' => 'gwrthdröer $1 {{PLURAL:$1||golygiad|olygiad|golygiad|golygiad|golygiad}}',
+'rollbacklinkcount-morethan' => 'gwrthdröer mwy na $1 {{PLURAL:$1||golygiad|olygiad|golygiad|golygiad|golygiad}}',
 'rollbackfailed' => 'Methodd y gwrthdroi',
 'cantrollback' => "Wedi methu gwrthdroi'r golygiad; y cyfrannwr diwethaf oedd unig awdur y dudalen hon.",
 'alreadyrolled' => "Nid yw'n bosib dadwneud y golygiad diwethaf i'r dudalen [[:$1|$1]] gan [[User:$2|$2]] ([[User talk:$2|Sgwrs]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);
index 1357ea7..9410653 100644 (file)
@@ -451,6 +451,10 @@ $1',
 'youhavenewmessages' => 'Du har $1 ($2).',
 'newmessageslink' => 'nye beskeder',
 'newmessagesdifflink' => 'ændringer siden sidste visning',
+'youhavenewmessagesfromusers' => 'Du har $1 fra {{PLURAL:$3|en anden bruger| $3 brugere}} ($2).',
+'youhavenewmessagesmanyusers' => 'Du har $1 fra mange brugere ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|en ny besked|nye beskeder}}',
+'newmessagesdifflinkplural' => 'seneste {{PLURAL:$1|ændring|ændringer}}',
 'youhavenewmessagesmulti' => 'Du har nye beskeder på $1',
 'editsection' => 'redigér',
 'editold' => 'redigér',
@@ -842,6 +846,10 @@ eller [{{fullurl:{{FULLPAGENAME}}|action=edit}} oprette siden]</span>.',
 'noarticletext-nopermission' => 'Der er i øjeblikket ikke noget tekst på denne side.
 Du kan [[Special:Search/{{PAGENAME}}|søge efter denne sides titel]] på andre sider,
 eller <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} se de relaterede loglister]</span>.',
+'missing-revision' => 'Revision #$1 af siden med navnet "{{PAGENAME}}" eksisterer ikke.
+
+Dette skyldes normalt at et forældet historik-link er fulgt til en side der er slettet.
+Detaljer kan findes i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} sletningsloggen].',
 'userpage-userdoesnotexist' => 'Brugerkontoen "<nowiki>$1</nowiki>" findes ikke. Overvej om du ønsker at oprette eller redigere denne side.',
 'userpage-userdoesnotexist-view' => 'Brugerkontoen "$1" er ikke oprettet.',
 'blocked-notice-logextract' => 'Denne bruger er i øjeblikket blokeret.
@@ -955,8 +963,13 @@ Der bør være færre end {{PLURAL:$2|$2 kald}}, lige nu er der {{PLURAL:$1|$1 k
 'parser-template-loop-warning' => 'Skabelonløkke fundet: [[$1]]',
 'parser-template-recursion-depth-warning' => 'En skabelon er rekursivt inkluderet for mange gange ($1)',
 'language-converter-depth-warning' => 'Dybdegrænse for sprogkonvertering overskredet ($1)',
+'node-count-exceeded-category' => 'Sider hvor antal noder er overskredet',
+'node-count-exceeded-warning' => 'Sider der har overskredet antallet af noder',
 'expansion-depth-exceeded-category' => 'Sider, der overskrider ekspansionsdybden',
 'expansion-depth-exceeded-warning' => 'Siden overskred ekspansionsdybden',
+'parser-unstrip-loop-warning' => 'Unstrip-loop opdaget',
+'parser-unstrip-recursion-limit' => 'Unstrip rekursionsgrænse er nået ($1)',
+'converter-manual-rule-error' => 'Fejl opdaget i manuel sprogkonvertingsregel',
 
 # "Undo" feature
 'undo-success' => 'Redigeringen kan fjernes.
@@ -1140,6 +1153,10 @@ Vær opmæksom på at bevare kontinuiteten i sidehistorikken.
 'editundo' => 'fjern redigering',
 'diff-multi' => '({{PLURAL:$1|En mellemliggende version|$1 mellemliggende versioner}} af {{PLURAL:$2|en bruger|$2 brugere}} ikke vist)',
 'diff-multi-manyusers' => '({{PLURAL:$1|En mellemliggende version|$1 mellemliggende versioner}} af mere end $2 {{PLURAL:$2|bruger|brugere}} ikke vist)',
+'difference-missing-revision' => '{{PLURAL:$2|En revision|$2 revisioner}} af denne forskel ($1) {{PLURAL:$2|blev|blev}} ikke fundet.
+
+Dette skyldes normalt et forældet diff link til en side der er slettet.
+Detaljer kan findes i [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} sletningsloggen].',
 
 # Search results
 'searchresults' => 'Søgeresultater',
@@ -1406,6 +1423,7 @@ Hvis du vælger at oplyse dit navn, vil det blive brugt til at tilskrive dig dit
 'right-writeapi' => 'Bruge redigeringsdelen af API',
 'right-delete' => 'Slette sider',
 'right-bigdelete' => 'Slette sider med mange versioner',
+'right-deletelogentry' => 'Slet og gendan specifikke log-poster',
 'right-deleterevision' => 'Slette og gendanne enkelte versioner af sider',
 'right-deletedhistory' => 'Se slettede verioner, uden at vise versionens indhold.',
 'right-deletedtext' => 'Vise slettet tekst og ændringer i slettede revisioner',
@@ -1686,6 +1704,11 @@ Informer venligst en [[Special:ListUsers/sysop|systemadministrator]].',
 'backend-fail-internal' => 'En ukendt fejl opstod i filbackend "$1".',
 'backend-fail-contenttype' => 'Kunne ikke bestemme typen af indhold i filen, der skal gemmes på "$1".',
 'backend-fail-batchsize' => 'Lagringsbackend gav en batch på $1 fil{{PLURAL:$1|operation|operationer}}; grænsen er $2 {{PLURAL:$2|operation|operationer}}.',
+'backend-fail-usable' => 'Kunne ikke skrive til filen "$1" på grund af manglende rettigheder eller manglende mapper/containere.',
+
+# File journal errors
+'filejournal-fail-dbconnect' => 'Kunne ikke tilslutte til journal databasen for lager backenden "$1".',
+'filejournal-fail-dbquery' => 'Kunne ikke opdatere journal databasen for lager backenden "$1".',
 
 # Lock manager
 'lockmanager-notlocked' => 'Kunne ikke låse "$1" op, da den ikke er låst.',
@@ -1696,6 +1719,7 @@ Informer venligst en [[Special:ListUsers/sysop|systemadministrator]].',
 'lockmanager-fail-releaselock' => 'Kunne ikke frigive låsen for "$1".',
 'lockmanager-fail-db-bucket' => 'Kunne ikke kontakte nok låsedatabaser i bøtten $1.',
 'lockmanager-fail-db-release' => 'Kunne ikke frigive lås til databasen $1.',
+'lockmanager-fail-svr-acquire' => 'Kunne ikke hente lås på serveren $1.',
 'lockmanager-fail-svr-release' => 'Kunne ikke frigive låse til serveren $1.',
 
 # ZipDirectoryReader
@@ -2235,6 +2259,8 @@ Bekræft venligst at du virkelig vil gøre dette, at du forstår konsekvenserne,
 'rollback' => 'Fjern redigeringer',
 'rollback_short' => 'Fjern redigering',
 'rollbacklink' => 'rul tilbage',
+'rollbacklinkcount' => 'tilbagefør $1 {{PLURAL:$1|redigering|redigeringer}}',
+'rollbacklinkcount-morethan' => 'tilbagefør mere end $1 {{PLURAL:$1|redigering|redigeringer}}',
 'rollbackfailed' => 'Kunne ikke fjerne redigeringen',
 'cantrollback' => 'Kan ikke fjerne redigering; den sidste bruger er den eneste forfatter.',
 'alreadyrolled' => 'Kan ikke fjerne den seneste redigering af [[:$1]] foretaget af [[User:$2|$2]] ([[User talk:$2|diskussion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);
@@ -3729,6 +3755,7 @@ Ellers kan du bruge den enkle formular nedenfor. Din kommentar vil blive tilføj
 'api-error-emptypage' => 'Det er ikke tilladt at oprette nye, tomme sider.',
 'api-error-fetchfileerror' => 'Intern fejl: noget gik galt under hentningen af filen.',
 'api-error-fileexists-forbidden' => 'En fil med navnet "$1" findes allerede, og den kan ikke overskrives.',
+'api-error-fileexists-shared-forbidden' => 'En fil med navnet "$1" eksisterer allerede i det delte filsystem og kan ikke overskrives.',
 'api-error-file-too-large' => 'Den fil du indsendte var for stor.',
 'api-error-filename-tooshort' => 'Filnavnet er for kort.',
 'api-error-filetype-banned' => 'Denne type fil er ikke tilladt.',
@@ -3768,5 +3795,5 @@ Ellers kan du bruge den enkle formular nedenfor. Din kommentar vil blive tilføj
 'duration-millennia' => '$1 {{PLURAL:$1|årtusind|årtusinder}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 er {{PLURAL:$4|en uønsket filtype|uønskede filtyper}}. {{PLURAL:$3|Den tilladte filtype|De tilladte filtyper}} er $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|er ikke en tilladt filtype|er ikke tilladte filtyper}}. Tilladt {{PLURAL:$3|filtype er|filtyper er}} $2.',
 );
index 674ff8d..fc5c7bd 100644 (file)
@@ -664,6 +664,10 @@ Siehe die [[Special:Version|Versionsseite]]',
 'youhavenewmessages' => 'Du hast $1 ($2).',
 'newmessageslink' => 'neue Nachrichten',
 'newmessagesdifflink' => 'Letzte Änderung',
+'youhavenewmessagesfromusers' => 'Du hast $1 von {{PLURAL:$3|einem anderen Benutzer|$3 Benutzern}} ($2).',
+'youhavenewmessagesmanyusers' => 'Du hast $1 von vielen Benutzern ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|eine neue Nachricht|neue Nachrichten}}',
+'newmessagesdifflinkplural' => 'letzte {{PLURAL:$1|Änderung|Änderungen}}',
 'youhavenewmessagesmulti' => 'Du hast neue Nachrichten: $1',
 'editsection' => 'Bearbeiten',
 'editold' => 'Bearbeiten',
@@ -1177,7 +1181,7 @@ Sie darf nicht mehr als $2 {{PLURAL:$2|Aufruf|Aufrufe}} haben, es {{PLURAL:$1|is
 'expansion-depth-exceeded-warning' => 'Die Seite hat die Expansionstiefe überschritten.',
 'parser-unstrip-loop-warning' => 'Zirkelbezug festgestellt',
 'parser-unstrip-recursion-limit' => 'Rekursionsgrenze beim Auflösen überschritten ($1)',
-'converter-manual-rule-error' => 'Bei der manuellen Sprachumwandlungsregel wurde ein Fehler entdeckt',
+'converter-manual-rule-error' => 'Bei der manuellen Sprachkonvertierungsregel wurde ein Fehler entdeckt.',
 
 # "Undo" feature
 'undo-success' => 'Die Bearbeitung kann rückgängig gemacht werden.
index 6ba63db..7904da9 100644 (file)
@@ -324,7 +324,7 @@ $messages = array(
 
 # Font style option in Special:Preferences
 'editfont-style' => 'Cayê vurnayışi de tipê nuştışi:',
-'editfont-default' => 'Fereziya cıgeyrayoği',
+'editfont-default' => 'Hesıbyayiya rovıteri',
 'editfont-monospace' => 'Tipê nustey sabıtcagırewtoği',
 'editfont-sansserif' => 'Tipê nustey Sans-serifi',
 'editfont-serif' => 'Tipê nustey Serifi',
@@ -416,7 +416,7 @@ $messages = array(
 
 # Cologne Blue skin
 'qbfind' => 'Bıvêne',
-'qbbrowse' => 'Çım ra viyarne',
+'qbbrowse' => 'Rovete',
 'qbedit' => 'Bıvurne',
 'qbpageoptions' => 'Ena pele',
 'qbpageinfo' => 'Gıre',
@@ -542,6 +542,10 @@ $1',
 'youhavenewmessages' => 'To rê estê $1 ($2).',
 'newmessageslink' => 'mesacê newey',
 'newmessagesdifflink' => 'vurnayışo peyên',
+'youhavenewmessagesfromusers' => 'Zey $1 ra {{PLURAL:$3|zewbi karber|$3 karberi}} ($2) esto.',
+'youhavenewmessagesmanyusers' => '$1 ra tay karberi ($2) dı estê.',
+'newmessageslinkplural' => '{{PLURAL:$1|yew mesac|mesacê newey}}',
+'newmessagesdifflinkplural' => 'peyni {{PLURAL:$1|vurnayış|vurnayışi}}',
 'youhavenewmessagesmulti' => '$1 mesaco newe esto',
 'editsection' => 'bıvurne',
 'editsection-brackets' => '[$1]',
@@ -933,6 +937,10 @@ ya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele bıvurne]</span>.',
 'noarticletext-nopermission' => 'No pel nıka veng o.
 No sernuşte şıma [[Special:Search/{{PAGENAME}}|pelanê binan de eşkeni bıgeri]]
 ya zi <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} itara eşekeni bıgere].</span>',
+'missing-revision' => 'Rewizyonê name dê pela da #$1 "{{PAGENAME}}" dı çıniyo.
+
+No normal de tarix dê pelanê besterneyan dı ena xırabin asena.
+Detayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.',
 'userpage-userdoesnotexist' => 'Hesabê karberi "<nowiki>$1</nowiki>" qeyd nêbiyo.
 Kerem ke, tı ke wazenay na pele bafernê/bıvurnê, qontrol ke.',
 'userpage-userdoesnotexist-view' => 'Hesabê karberi "$1" qeyd nêbiyo.',
@@ -1050,6 +1058,7 @@ Taye şabloni zerre pel de nêmociyayeni.',
 'expansion-depth-exceeded-warning' => 'Ravêriya pela xori herayêna',
 'parser-unstrip-loop-warning' => 'Unstrip lete vineya',
 'parser-unstrip-recursion-limit' => 'Sinorê limit dê qayış dê ($1) ravêrya',
+'converter-manual-rule-error' => 'Rehberê zıwan açarnayışi dı xırabin tesbit biya',
 
 # "Undo" feature
 'undo-success' => 'No vurnayiş tepeye geryeno. pêverronayişêyê cêrıni kontrol bıkeri.',
@@ -1236,6 +1245,10 @@ no vurnayişo ke şıma keni kontrol bıkere yew pelo kehen nêbo.',
 'editundo' => 'peyser bia',
 'diff-multi' => '({{PLURAL:$1|Yew revizyono miyanên|$1 revizyonê miyanêni}} terefê {{PLURAL:$2|yew karberi|$2 karberan}} nêmocno)',
 'diff-multi-manyusers' => '({{PLURAL:$1|jew timar kerdışo qıckeko|$1 timar kerdışo qıckeko}} timar kerdo, $2 {{PLURAL:$2|Karber|karberi}} memocne)',
+'difference-missing-revision' => 'Ferqê {{PLURAL:$2|Yew rewizyonê|$2 rewizyonê}} {{PLURAL:$2|dı|dı}} ($1) sero çıniyo.
+
+No normal de werênayış dê pelanê besterneyan dı ena xırabin asena.
+Detayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.',
 
 # Search results
 'searchresults' => 'Neticeyê geyrayışi',
@@ -1466,7 +1479,7 @@ Kaberê bini ke şıma de kewti irtıbat, adresa e-postey şıma eşkera nêbena
 'group-bot' => 'Boti',
 'group-sysop' => 'İdarekari',
 'group-bureaucrat' => 'Burokrati',
-'group-suppress' => 'Çımpawiyayeni',
+'group-suppress' => 'Çımpawıteni',
 'group-all' => '(pêro)',
 
 'group-user-member' => '{{GENDER:$1|karber}}',
@@ -2103,7 +2116,7 @@ Xo vira mekerê ke, sıteyê webiê bini şenê direkt ebe URLi yew dosya ra gı
 'nopagetext' => 'pelê hedefi ke şıma nişane kerdo çin o.',
 'pager-newer-n' => '{{PLURAL:$1|newiyer 1|newiyer $1}}',
 'pager-older-n' => '{{PLURAL:$1|Kıhanyer 1|Kıhanyer $1}}',
-'suppress' => 'Kontrol',
+'suppress' => 'Çımpawıten',
 'querypage-disabled' => 'Na pelaya xısusi,sebeb de performansi ra qefılneyê.',
 
 # Book sources
@@ -2135,7 +2148,7 @@ tipa rocaneyi, nameyê karberi (herfa pil u qıci re hessas a), ya zi peli (reyn
 'allarticles' => 'Peli pêro',
 'allinnamespace' => 'Peli pênro ( $1 cayênameyî)',
 'allnotinnamespace' => 'Pelanê hemî ($1 cayênameyî de niyo)',
-'allpagesprev' => 'Verêni',
+'allpagesprev' => 'Verên',
 'allpagesnext' => 'ver şo',
 'allpagessubmit' => 'Biya',
 'allpagesprefix' => 'herfê ke şıma tiya de nuşti, pê ney herfan pelê ke destpêkenê liste ker:',
@@ -2234,7 +2247,7 @@ qey heqê şexsi de [[{{MediaWiki:Listgrouprights-helppage}}|hema malumato ziyed
 'email-legend' => 'karberê {{SITENAME}} binan re e-posta bıerşaw',
 'emailfrom' => 'Rışten:',
 'emailto' => 'Geren:',
-'emailsubject' => 'behs/mesela:',
+'emailsubject' => 'Mersel:',
 'emailmessage' => 'Mesaj',
 'emailsend' => 'bıerşawê/bıruşnê',
 'emailccme' => 'kopyayekê mesaji mı re bıerşaw',
@@ -4057,7 +4070,7 @@ Ena sita dı newke xırabiya teknik esta.',
 
 # Feedback
 'feedback-bugornote' => 'Jew mersela teferruato teknik esta şıma reca malumatê şıma hazıro se [ $1  jew xırab rapor] bıvinê.Zewbi zi, formê cerê xo rê şenê karfiyê. Vatışê xo pela da "[ $3  $2 ]", namey karber dê xoya piya u wasteriya karfiye.',
-'feedback-subject' => 'Muhtewa:',
+'feedback-subject' => 'Mersel:',
 'feedback-message' => 'Mesac:',
 'feedback-cancel' => 'Bıterkne',
 'feedback-submit' => 'Peyxeberdar Bırşe',
@@ -4122,5 +4135,5 @@ Ena sita dı newke xırabiya teknik esta.',
 'duration-millennia' => '$1 {{PLURAL:$1|milenyum|milenyumi}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|Ena babeta dosya qebul ne vinena|Ena babeta dosya qebul ne vinena|Ena babeta dosya qebul ne vinena}}. Eke cırê izin deyayo $2. {{PLURAL:$3|Babatan dosyayan|babeta dosyayan}}',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|Dosya qebul ne vinena|dosya qebul ne vinena|Ena babeta dosya qebul ne vinena}}. Eke cırê izin deyayo se {{PLURAL:$3|Babatan dosyayan|babeta dosyayan}} de $2 bıvin.',
 );
index 4172e96..f9285f1 100644 (file)
@@ -913,6 +913,7 @@ Njesmějo daś wěcej nježli $2 {{PLURAL:$2|wołanja|wołanjowu|wołanjow|woła
 'expansion-depth-exceeded-warning' => 'Bok jo ekspansisku dłymokosć pśekšocył',
 'parser-unstrip-loop-warning' => 'Njeskóńcna kokula namakana',
 'parser-unstrip-recursion-limit' => 'Rekursiska granica pśekšocona ($1)',
+'converter-manual-rule-error' => 'Zmólka w manuelnem pšawidle rěcnego konwertěrowanja namakana',
 
 # "Undo" feature
 'undo-success' => 'Wobźěłanje móžo se wótpóraś. Pšosym pśeglěduj dołojcne pśirownowanje aby se wěsty był, až to wót wěrnosći coš, a pón składuj změny, aby se wobźěłanje doskóńcnje wótpórało.',
index 91de9a1..81b4f23 100644 (file)
@@ -911,6 +911,10 @@ See [[Special:Version|version page]].',
 'youhavenewmessages'           => 'You have $1 ($2).',
 'newmessageslink'              => 'new messages',
 'newmessagesdifflink'          => 'last change',
+'youhavenewmessagesfromusers'  => 'You have $1 from {{PLURAL:$3|another user|$3 users}} ($2).',
+'youhavenewmessagesmanyusers'  => 'You have $1 from many users ($2).',
+'newmessageslinkplural'        => '{{PLURAL:$1|a new message|new messages}}', # don't rely on the value of $1, it's 1 for singular and 2 for "more than one"
+'newmessagesdifflinkplural'    => 'last {{PLURAL:$1|change|changes}}', # don't rely on the value of $1, it's 1 for singular and 2 for "more than one"
 'youhavenewmessagesmulti'      => 'You have new messages on $1',
 'newtalkseparator'             => ',&#32;', # do not translate or duplicate this message to other languages
 'editsection'                  => 'edit',
@@ -2191,7 +2195,7 @@ Permitted {{PLURAL:$3|file type is|file types are}} $2.',
 'hookaborted'                 => 'The modification you tried to make was aborted by an extension.',
 'illegal-filename'            => 'The filename is not allowed.',
 'overwrite'                   => 'Overwriting an existing file is not allowed.',
-'unknown-error'               => 'An unknown error occured.',
+'unknown-error'               => 'An unknown error occurred.',
 'tmp-create-error'            => 'Could not create temporary file.',
 'tmp-write-error'             => 'Error writing temporary file.',
 'large-file'                  => 'It is recommended that files are no larger than $1;
@@ -2289,7 +2293,7 @@ Please verify that the URL is valid and accessible and try again.
 If the problem persists, contact an [[Special:ListUsers/sysop|administrator]].',
 'upload-too-many-redirects'         => 'The URL contained too many redirects',
 'upload-unknown-size'               => 'Unknown size',
-'upload-http-error'                 => 'An HTTP error occured: $1',
+'upload-http-error'                 => 'An HTTP error occurred: $1',
 'upload-copy-upload-invalid-domain' => 'Copy uploads are not available from this domain.',
 
 # File backend
@@ -4702,7 +4706,7 @@ Images are shown in full resolution, other file types are started with their ass
 * <span class="mw-specialpagerestricted">Restricted special pages.</span>',
 'specialpages-group-maintenance' => 'Maintenance reports',
 'specialpages-group-other'       => 'Other special pages',
-'specialpages-group-login'       => 'Login / sign up',
+'specialpages-group-login'       => 'Login / create account',
 'specialpages-group-changes'     => 'Recent changes and logs',
 'specialpages-group-media'       => 'Media reports and uploads',
 'specialpages-group-users'       => 'Users and rights',
index 365a65b..06c2b32 100644 (file)
@@ -326,7 +326,7 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Ŝalti sekcian redaktadon per dekstra musklako de sekciaj titoloj (kun JavaScript)',
 'tog-showtoc' => 'Montri enhavliston (por paĝoj kun pli ol 3 sekcioj)',
 'tog-rememberpassword' => 'Memori mian ensalutadon ĉe ĉi tiu retumilo (daŭrante maksimume $1 {{PLURAL:$1|tagon|tagojn}})',
-'tog-watchcreations' => 'Aldoni de mi kreitajn paĝojn al mia atentaro',
+'tog-watchcreations' => 'Aldoni miajn kreatajn paĝojn al mia atentaro',
 'tog-watchdefault' => 'Aldoni al mia atentaro paĝojn redaktitajn de mi',
 'tog-watchmoves' => 'Aldoni paĝojn, kiujn mi movas, al mia atentaro',
 'tog-watchdeletion' => 'Aldoni paĝojn, kiujn mi forigas, al mia atentaro',
@@ -334,9 +334,9 @@ $messages = array(
 'tog-previewontop' => 'Montri antaŭrigardon antaŭ redaktilo',
 'tog-previewonfirst' => 'Montri antaŭrigardon je unua redakto',
 'tog-nocache' => 'Malŝalti retumilan kaŝmemoron de paĝoj',
-'tog-enotifwatchlistpages' => 'Sendi al mi retmesaĝon kiam tiu paĝo estas ŝanĝita',
+'tog-enotifwatchlistpages' => 'Sendi al mi retmesaĝon kiam tiu paĝo en mia atentaro estas ŝanĝita',
 'tog-enotifusertalkpages' => 'Sendi al mi retmesaĝon kiam mia diskutpaĝo estas ŝanĝita',
-'tog-enotifminoredits' => 'Sendi al mi ankaŭ retmesaĝojn por malgrandaj redaktoj de paĝoj',
+'tog-enotifminoredits' => 'Sendi al mi ankaŭ retmesaĝojn pro malgrandaj redaktoj de paĝoj',
 'tog-enotifrevealaddr' => 'Malkaŝi mian retadreson en informaj retpoŝtaĵoj',
 'tog-shownumberswatching' => 'Montri la nombron da priatentaj uzantoj',
 'tog-oldsig' => 'Ekzistanta subskribo:',
@@ -576,6 +576,10 @@ $1',
 'youhavenewmessages' => 'Por vi estas $1 ($2).',
 'newmessageslink' => 'novaj mesaĝoj',
 'newmessagesdifflink' => 'ŝanĝoj kompare kun antaŭlasta versio',
+'youhavenewmessagesfromusers' => 'Riceviĝis $1 de {{PLURAL:$3|alia uzanto|$3 uzantoj}} ($2).',
+'youhavenewmessagesmanyusers' => 'Riceviĝis $1 de multaj uzantoj ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|nova mesaĝo|novaj mesaĝoj}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|lasta ŝanĝo|lastaj ŝanĝoj}}',
 'youhavenewmessagesmulti' => 'Vi havas novajn mesaĝojn ĉe $1',
 'editsection' => 'redakti',
 'editold' => 'redakti',
@@ -673,6 +677,8 @@ Bonvolu raporti ĉi tiun al [[Special:ListUsers/sysop|administranto]], notante l
 'cannotdelete' => 'Ne eblis forigi la elektitan paĝon aŭ dosieron "$1".
 Eble ĝi estis jam forigita de iu alia.',
 'cannotdelete-title' => 'Ne eblas forigi paĝon "$1"',
+'delete-hook-aborted' => 'Forigo ĉesigis per hoko.
+Ĝi ne donis eksplikon.',
 'badtitle' => 'Fuŝa titolo',
 'badtitletext' => 'La petita paĝotitolo estis malvalida, malplena, aŭ malĝuste ligita interlingva aŭ intervikia titolo.
 Ĝi eble enhavas unu aŭ pliaj signoj kiu ne povas esti uzata en titoloj.',
@@ -701,8 +707,13 @@ $2',
 'ns-specialprotected' => 'Paĝoj en la {{ns:special}} nomspaco ne povas esti redaktataj.',
 'titleprotected' => "Ĉi titolo estas protektita de kreado de [[User:$1|$1]].
 La kialo donata estis ''$2''.",
+'filereadonlyerror' => 'La dosiero "$1" ne estas modifebla, ĉar la datumbazujo "$2" estas en nurlegebla modo.
+
+La administranto kiu ŝlosis ĝin proponis tiun klarigon: "$3".',
 'invalidtitle-knownnamespace' => 'Nevalida titolo kun nomspaco "$2" kaj teksto "$3"',
 'invalidtitle-unknownnamespace' => 'Nevalida titolo kun nekonata nomspaca numero $1 kaj teksto "$2"',
+'exception-nologin' => 'Ne ensalutita',
+'exception-nologin-text' => 'Vi devas ensaluti ĉi tiun vikion por fari ĉi tiun agon.',
 
 # Virus scanner
 'virus-badscanner' => "Malbona konfiguro: nekonata virusa skanilo: ''$1''",
@@ -723,6 +734,7 @@ Ne forgesu fari viajn [[Special:Preferences|{{SITENAME}}-preferojn]].',
 'remembermypassword' => 'Memori mian ensalutadon ĉe ĉi tiu komputilo (daŭrante maksimume $1 {{PLURAL:$1|tagon|tagojn}})',
 'securelogin-stick-https' => 'Resti konektita al HTTPS post ensalutado',
 'yourdomainname' => 'Via domajno',
+'password-change-forbidden' => 'Ve ne povas ŝanĝi pasvortojn en ĉi tiu vikio.',
 'externaldberror' => 'Aŭ estis datenbaza eraro rilate al ekstera aŭtentikigado, aŭ vi ne rajtas ĝisdatigi vian eksteran konton.',
 'login' => 'Ensaluti',
 'nav-login-createaccount' => 'Ensaluti / Krei novan konton',
@@ -970,6 +982,10 @@ aŭ [{{fullurl:{{FULLPAGENAME}}|action=edit}} redakti ĉi tiun paĝon]</span>.',
 'noarticletext-nopermission' => 'Estas neniom da teksto en ĉi tiu paĝo.
 Vi povas [[Special:Search/{{PAGENAME}}|serĉi ĉi tiun paĝan titolon]] en aliaj paĝoj,
 aŭ <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serĉi la rilatajn protokolojn]</span>.',
+'missing-revision' => 'La revizio n-ro $1 de la paĝo nomata "{{PAGENAME}}" ne ekzistas.
+
+La kutima kaŭzo estas sekvi malaktualan historio-ligilon al paĝo forviŝita.
+Detaloj troveblos en la [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de forviŝoj].',
 'userpage-userdoesnotexist' => 'Uzantokonto "<nowiki>$1</nowiki>" ne estas registrita. Bonvolu konfirmi se vi volas krei/redakti ĉi tiun paĝon.',
 'userpage-userdoesnotexist-view' => 'Uzanto-konto "$1" ne estas registrita.',
 'blocked-notice-logextract' => 'Ĉi tiu uzanto estas ĉi-momente forbarita.
@@ -1076,6 +1092,7 @@ Verŝajne ĝi estis forigita.',
 'edit-no-change' => 'Via redakto estis ignorita, ĉar neniu ŝanĝo estis farita al la teksto.',
 'edit-already-exists' => 'Ne eblis krei novan paĝon.
 Ĝi jam ekzistas.',
+'defaultmessagetext' => 'Defaŭlta teksto',
 
 # Parser/template warnings
 'expensive-parserfunction-warning' => 'Averto: Ĉi tiu paĝo enhavas tro da multekostaj sintaksaj funkcio-vokoj.
@@ -1095,6 +1112,9 @@ Iuj ŝablonoj ne estos inkluzivitaj.',
 'node-count-exceeded-warning' => 'Paĝo preterpasis la nombron da nodoj.',
 'expansion-depth-exceeded-category' => 'Paĝoj en kiuj la ekpansiprofundo estas preterpasita',
 'expansion-depth-exceeded-warning' => 'Paĝo preterpasis la ekpansiprofundon.',
+'parser-unstrip-loop-warning' => 'Cirkloreferencon detektis',
+'parser-unstrip-recursion-limit' => 'Rikurlimiton de analizopoj ($1) superis',
+'converter-manual-rule-error' => 'Eraron detektis en mana lingvokonverta regulo',
 
 # "Undo" feature
 'undo-success' => 'La redakto estas malfarebla.
@@ -1281,6 +1301,10 @@ Certigu ke ĉi tiu ŝanĝo tenos kontinuecon de la historia paĝo.',
 'editundo' => 'malfari',
 'diff-multi' => '({{PLURAL:$1|Unu intermeza versio|$1 intermezaj versioj}} de {{PLURAL:$2|unu uzanto|$2 uzantoj}} ne estas {{PLURAL:$1|montrata|montrataj}}.)',
 'diff-multi-manyusers' => '({{PLURAL:$1|Unu intermeza versio|$1 intermezaj versioj}} de pli ol {{PLURAL:$2|unu uzanto|$2 uzantoj}} ne estas {{PLURAL:$1|montrata|montrataj}}.)',
+'difference-missing-revision' => '{{PLURAL:$2|Unu revizio|$2 revizioj}} de ĉi tiu malsameco ($1) ne {{PLURAL:$2|estis|estis}} trovebla.
+
+La kutima kaŭzo estas sekvi malaktualan malsamo-ligilon al paĝo forviŝita.
+Detaloj troveblos en la [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de forviŝoj].',
 
 # Search results
 'searchresults' => 'Serĉrezultoj',
@@ -1548,6 +1572,7 @@ Jen hazarde generita valoro por via uzo: $1',
 'right-writeapi' => 'Uzi la API por modifi la vikion',
 'right-delete' => 'Forigi paĝojn',
 'right-bigdelete' => 'Forigi paĝojn kun grandaj historioj',
+'right-deletelogentry' => 'Forigi kaj malforigi specifajn enmetojn en la registro.',
 'right-deleterevision' => 'Forigi kaj malforigi specifajn versiojn de paĝoj',
 'right-deletedhistory' => 'Rigardi listanojn de forigitaj historioj, sen ties asociaj tekstoj',
 'right-deletedtext' => 'Rigardi forigitan tekston kaj ŝanĝojn inter forigitaj revizioj.',
@@ -1806,6 +1831,7 @@ Se la problemo kontinuas, kontaku [[Special:ListUsers/sysop|sisteman administran
 'upload-too-many-redirects' => 'La URL-o enhavis tro multajn alidirektilojn',
 'upload-unknown-size' => 'Nekonata grandeco',
 'upload-http-error' => 'HTTP-eraro okazis: $1',
+'upload-copy-upload-invalid-domain' => 'Kopio-alŝutoj ne disponiĝas el ĉi tiu domajno.',
 
 # File backend
 'backend-fail-stream' => 'Ne povis fluigi dosieron $1.',
@@ -1824,6 +1850,18 @@ Se la problemo kontinuas, kontaku [[Special:ListUsers/sysop|sisteman administran
 'backend-fail-closetemp' => 'Ne povis fermi provizoran dosieron.',
 'backend-fail-read' => 'Ne povas legi dosieron "$1".',
 'backend-fail-create' => 'Ne povas skribi dosieron $1.',
+'backend-fail-maxsize' => 'Ne povis skribi la dosieron "$1," ĉar ĝi estas pli granda ol {{plural: $2|bitoko|$2 bitokoj}}.',
+'backend-fail-readonly' => 'La interna konservujo "$1" nune estas nurlega. La indikata kialo estas: "\'\'$2\'\'"',
+'backend-fail-synced' => 'La dosiero "$1" estas en nekohera stato kun la internaj konservujoj',
+'backend-fail-connect' => 'Ne eblis konekti la internan konservujon "$1".',
+'backend-fail-internal' => 'Nekonata eraro okazis en interna konservujo "$1".',
+'backend-fail-contenttype' => 'Ne eblis determini la enhavo-tipo de la dosiero por konservi ĉe "$1".',
+'backend-fail-batchsize' => 'Interna konservujo estis donita komandaron de $1 {{PLURAL:$1|dosiera operacio|dosieraj operacioj}}; la limo estas $2 {{PLURAL:$2|operacio|operacioj}}.',
+'backend-fail-usable' => 'Ne eblis skribi dosieron "$1" pro malsufiĉaj permesoj aŭ mankantaj dosierujoj.',
+
+# File journal errors
+'filejournal-fail-dbconnect' => 'Ne eblis konekti la protokolan datumbazon por la ekstera konservujo "$1".',
+'filejournal-fail-dbquery' => 'Ne eblis ĝisdatigi la protokolan datumbazon por la ekstera konservujo "$1".',
 
 # Lock manager
 'lockmanager-notlocked' => 'Ne povis malŝlosi "$1"; ĝi ne estas ŝlosita.',
@@ -1834,6 +1872,7 @@ Se la problemo kontinuas, kontaku [[Special:ListUsers/sysop|sisteman administran
 'lockmanager-fail-releaselock' => 'Ne povis liberigi ŝlosadon por "$1".',
 'lockmanager-fail-db-bucket' => 'Ne povis kontakti sufiĉajn ŝlos-datumbazojn en ujo $1.',
 'lockmanager-fail-db-release' => 'Ne povis liberigi ŝlosadojn de datumbazao $1.',
+'lockmanager-fail-svr-acquire' => 'Ne povis akiri ŝlosadojn de servilo $1.',
 'lockmanager-fail-svr-release' => 'Ne povis liberigi ŝlosadojn de servilo $1.',
 
 # ZipDirectoryReader
@@ -1941,6 +1980,10 @@ La jena listo montras la {{PLURAL:$1|unua paĝligilo|unuaj $1 paĝligiloj}} al n
 Bonvolu vidi la [$2 dosier-priskriban paĝon] por plua informo.',
 'sharedupload-desc-here' => 'Ĉi tiu dosiero estas de $1 kaj estas uzebla de aliaj projektoj.
 Jen la priskribo en ties [$2 dosier-priskriba paĝo].',
+'sharedupload-desc-edit' => 'Ĉi tiu dosiero estas el $1 kaj estas uzebla en aliaj projektoj.
+Eble vi volas redakti la priskribon ĉe ties [$2 dosier-priskriba paĝo].',
+'sharedupload-desc-create' => 'Ĉi tiu dosiero estas el $1 kaj estas uzebla en aliaj projektoj.
+Eble vi volas redakti la priskribon ĉe ties [$2 dosier-priskriba paĝo].',
 'filepage-nofile' => 'Neniu dosiero de ĉi tiu nomo ekzistas.',
 'filepage-nofile-link' => 'Neniu dosiero de ĉi tiu nomo ekzistas, sed vi povas [$1 alŝuti ĝin].',
 'uploadnewversion-linktext' => 'Alŝuti novan version de ĉi tiu dosiero',
@@ -2073,6 +2116,8 @@ Paĝo estas traktata kiel apartigilo se ĝi uzas ŝablonon kiu estas ligita de [
 'wantedpages' => 'Dezirataj paĝoj',
 'wantedpages-badtitle' => 'Malvalida titolo en rezulta aro: $1',
 'wantedfiles' => 'Dezirataj dosieroj',
+'wantedfiletext-cat' => 'La jenaj dosieroj estas uzataj sed ne ekzistas. Dosieroj de eksteraj konservujoj eble estos listigita malgraŭ ne ekzistante. Tia malprave pozitivaj rezultoj estos <del>forstrekita</del>. Ankaŭ, paĝoj kiuj enmetas dosierojn kiuj ne ekzistas estas listigita en [[:$1]].',
+'wantedfiletext-nocat' => 'La jenaj dosieroj estas uzataj sed ne ekzistas. Dosieroj de eksteraj dosierujoj eble estas listigitaj malgraŭ eksistado. Tia malprave pozitiva rezulto estos <del>forstrekita</del>.',
 'wantedtemplates' => 'Dezirataj ŝablonoj',
 'mostlinked' => 'Plej ligitaj paĝoj',
 'mostlinkedcategories' => 'Plej ligitaj kategorioj',
@@ -2135,6 +2180,7 @@ komprenata kiel rekomendo aŭ reklamo.',
 Vi povas plistrikti la mendon per selektado de protokola speco, la salutnomo (inkluzivante uskladon) aŭ la efika paĝo (ankaŭ inkluzivas uskladon).',
 'logempty' => 'Neniaj artikoloj en la protokolo.',
 'log-title-wildcard' => 'Serĉi titolojn komencantajn kun ĉi tiu teksto',
+'showhideselectedlogentries' => 'Montri/kaŝi elektitajn protokolerojn',
 
 # Special:AllPages
 'allpages' => 'Ĉiuj paĝoj',
@@ -2155,6 +2201,8 @@ Vi povas plistrikti la mendon per selektado de protokola speco, la salutnomo (in
 'allpages-hide-redirects' => 'Kaŝi alidirektilojn',
 
 # SpecialCachedPage
+'cachedspecial-viewing-cached-ttl' => 'Vi vidas version de la paĝo el kaŝmemoro, kiu eble estis aktuala ekde $1.',
+'cachedspecial-viewing-cached-ts' => 'Vi vidas version de la paĝo el kaŝmemoro, kiu eble ne estas la plej aktuala.',
 'cachedspecial-refresh-now' => 'Vidas plej nova.',
 
 # Special:Categories
@@ -2363,6 +2411,8 @@ Vidu la paĝon $2 por registro de lastatempaj forigoj.',
 'rollback' => 'Restarigi antaŭan redakton',
 'rollback_short' => 'Malfari',
 'rollbacklink' => 'malfari',
+'rollbacklinkcount' => 'nuligi $1 {{PLURAL:$1|redakton|redaktojn}}',
+'rollbacklinkcount-morethan' => 'nuligi pli ol $1 {{PLURAL:$1|redakton|redaktojn}}',
 'rollbackfailed' => 'Malfaro malsukcesis',
 'cantrollback' => 'Ne povas restarigi antaŭan redakton; la redaktinto lasta estas la sola aŭtoro de la paĝo.',
 'alreadyrolled' => 'Ne povas restarigi la lastan redakton de [[:$1]] de la [[User:$2|$2]] ([[User talk:$2|diskuto]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);
@@ -2886,6 +2936,7 @@ Datoj de versioj kaj nomoj de redaktantoj estos preservitaj.
 'import-invalid-interwiki' => 'Ne povas importi de la specifita vikio.',
 'import-error-edit' => 'Paĝo "$1" ne estas importita ĉar vi ne rajtas radakti ĝin.',
 'import-error-create' => 'Paĝo "$1" ne estas importita ĉar vi ne rajtas krei ĝin.',
+'import-error-interwiki' => 'Paĝo "$1" ne estis importita pro sia nomo estas deklarita por ekstera ligado (intervikia).',
 'import-error-special' => 'Paĝo "$1" ne estas importata, ĉar ĝi apartenas al speciala nomspaco, kiu ne permesas paĝojn.',
 'import-error-invalid' => 'Paĝo "$1" ne estas importata, ĉar ĝia nomo estas malvalida.',
 
@@ -2901,6 +2952,9 @@ Datoj de versioj kaj nomoj de redaktantoj estos preservitaj.
 'javascripttest' => 'Ĝavoskripta testado',
 'javascripttest-disabled' => 'Ĉi tiu funkcio estas malŝaltita en ĉi tiu vikio.',
 'javascripttest-title' => 'Irigante $1 testoj',
+'javascripttest-pagetext-noframework' => 'Ĉi tiu paĝo estas konservita por funkciigi testojn de JavaScript.',
+'javascripttest-pagetext-unknownframework' => 'Nekonta test-framo "$1".',
+'javascripttest-pagetext-frameworks' => 'Bonvolu elekti unu el la jenaj test-framoj: $1',
 'javascripttest-pagetext-skins' => 'Elektu kun kio etoso irigi la testojn:',
 'javascripttest-qunit-intro' => 'Vidu [$1 testa dokumentaro] en mediawiki.org.',
 'javascripttest-qunit-heading' => 'Testaro QUnit por JavaScript de MediaWiki',
@@ -3032,6 +3086,7 @@ Datoj de versioj kaj nomoj de redaktantoj estos preservitaj.
 'spambot_username' => 'Trudmesaĝa forigo de MediaWiki',
 'spam_reverting' => 'Restarigo de lasta versio ne entenante ligilojn al $1',
 'spam_blanking' => 'Forviŝo de ĉiuj versioj entenantaj ligilojn al $1',
+'spam_deleting' => 'Ĉiuj versioj enhavis ligilojn al $1 - forigante',
 
 # Info page
 'pageinfo-title' => 'Informoj por "$1"',
@@ -3737,6 +3792,9 @@ Oni devis doni al vi [{{SERVER}}{{SCRIPTPATH}}/COPYING ekzempleron de la GNU Gen
 'version-software' => 'Instalita programaro',
 'version-software-product' => 'Produkto',
 'version-software-version' => 'Versio',
+'version-entrypoints' => 'Eniropunktaj URL-oj',
+'version-entrypoints-header-entrypoint' => 'Eniropunkto',
+'version-entrypoints-header-url' => 'Retadreso',
 
 # Special:FilePath
 'filepath' => 'Vojo al dosiero',
@@ -3898,6 +3956,8 @@ Aŭ vi povas uzi la facilan formularon sube. Via komento estos aldonita al la pa
 'api-error-empty-file' => 'La dosiero kiun vi sendis estis malplena.',
 'api-error-emptypage' => 'Kreo de novaj malplenaj paĝoj ne estas permesita.',
 'api-error-fetchfileerror' => 'Interna eraro: io misfunkciis dum la dosiera prenado.',
+'api-error-fileexists-forbidden' => 'Dosiero nomata "$1" jam existas kaj ne estas surskribebla.',
+'api-error-fileexists-shared-forbidden' => 'Dosiero nomata "$1" jam existas en la kunhavata dosier-datumbazo kaj ne estas surskribebla.',
 'api-error-file-too-large' => 'La dosiero kiun vi alŝutis estis tro granda.',
 'api-error-filename-tooshort' => 'La dosiernomo estas tro mallonga.',
 'api-error-filetype-banned' => 'Ĉi tiu tipo de dosiero estas malpermesita.',
@@ -3925,6 +3985,17 @@ Aŭ vi povas uzi la facilan formularon sube. Via komento estos aldonita al la pa
 'api-error-uploaddisabled' => 'Alŝutato estas malebligata en tiu ĉi vikio.',
 'api-error-verification-error' => 'Tiu ĉi dosiero eble estas difektita, aŭ havas la malĝustan dosieran finaĵon.',
 
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|sekundo|sekundoj}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minuto|minutoj}}',
+'duration-hours' => '$1 {{PLURAL:$1|horo|horoj}}',
+'duration-days' => '$1 {{PLURAL:$1|tago|tagoj}}',
+'duration-weeks' => '$1 {{PLURAL:$1|semajno|$1 semajnoj}}',
+'duration-years' => '$1 {{PLURAL:$1|jaro|jaroj}}',
+'duration-decades' => '$1 {{PLURAL:$1|jardeko|jardekoj}}',
+'duration-centuries' => '$1 {{PLURAL:$1|jarcento|jarcentoj}}',
+'duration-millennia' => '$1 {{PLURAL:$1|jarmilo|jarmiloj}}',
+
 # Unknown messages
 'api-error-filetype-banned-type' => '$1 ne estas {{PLURAL:$4|permesita dosiero-tipo|permesitaj dosiero-tipoj}}. {{PLURAL:$3|Permesita dosiero-tipo|Permesitaj dosiero-tipoj}} estas $2.',
 );
index da2b536..2873e48 100644 (file)
@@ -12,6 +12,7 @@
  * @author Alhen
  * @author Alpertron
  * @author Alvaro qc
+ * @author Amire80
  * @author Armando-Martin
  * @author Ascánder
  * @author Baiji
@@ -46,6 +47,7 @@
  * @author Invadinado
  * @author Jatrobat
  * @author Jens Liebenau
+ * @author Jewbask
  * @author Jurock
  * @author Kaganer
  * @author Lin linao
@@ -610,6 +612,8 @@ $1',
 'youhavenewmessages' => 'Tienes $1 ($2).',
 'newmessageslink' => 'mensajes nuevos',
 'newmessagesdifflink' => 'última modificación',
+'newmessageslinkplural' => '{{PLURAL:$1|un nuevo mensaje|mensajes nuevos}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|última modificación|últimos cambios}}',
 'youhavenewmessagesmulti' => 'Tienes nuevos mensajes en $1',
 'editsection' => 'editar',
 'editold' => 'editar',
@@ -1010,6 +1014,8 @@ o [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar esta página]</span>.',
 'noarticletext-nopermission' => 'Actualmente no hay texto en esta página.
 Puedes [[Special:Search/{{PAGENAME}}|buscar este título de página]] en otras páginas,
 o <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar en los registros relacionados]</span>.',
+'missing-revision' => 'La revisión # $1  de la página denominada "{{PAGENAME}}" no existe.
+!¡ N!Esto es generalmente causado al seguir un enlace de historia obsoleto a una página que se ha borrado.!¡ N!Los detalles pueden encontrarse en el [{{fullurl: {{#Special:Log}} / delete|page = {{FULLPAGENAMEE}}}} registro de borrado].',
 'userpage-userdoesnotexist' => 'La cuenta de usuario «<nowiki>$1</nowiki>» no está registrada. Por favor comprueba si quieres crear o editar esta página.',
 'userpage-userdoesnotexist-view' => 'La cuenta de usuario «$1» no está registrada.',
 'blocked-notice-logextract' => 'Este usuario está actualmente bloqueado.
@@ -1097,7 +1103,7 @@ Puede volver atrás y editar una página existente, [[Special:UserLogin|identifi
 'sectioneditnotsupported-text' => 'La edición de sección no es compatible con esta página.',
 'permissionserrors' => 'Errores de permisos',
 'permissionserrorstext' => 'No tienes permiso para hacer eso, por {{PLURAL:$1|el siguiente motivo|los siguientes motivos}}:',
-'permissionserrorstext-withaction' => 'No tienes permiso para $2, por los siguientes {{PLURAL:$1|motivo|motivos}}:',
+'permissionserrorstext-withaction' => 'No tienes permiso para $2, por {{PLURAL:$1|el siguiente motivo|los siguientes motivos}}:',
 'recreate-moveddeleted-warn' => "'''Atención: estás volviendo a crear una página que ha sido borrada anteriormente.'''
 
 Deberías considerar si es apropiado continuar editando esta página.
@@ -1135,6 +1141,7 @@ Ese o esos parámetros han sido omitidos.',
 'expansion-depth-exceeded-warning' => 'Página que ha superado la profundidad de expansión',
 'parser-unstrip-loop-warning' => 'Se ha detectado un bucle "unstrip"',
 'parser-unstrip-recursion-limit' => 'Se ha superado el límite de recursión de "unstrip" ($1)',
+'converter-manual-rule-error' => 'Error detectado en la regla de conversión manual del lenguaje',
 
 # "Undo" feature
 'undo-success' => 'La edición puede deshacerse. Antes de deshacer la edición, comprueba la siguiente comparación para verificar que realmente es lo que quieres hacer, y entonces guarda los cambios para así deshacer la edición.',
@@ -1323,6 +1330,8 @@ Nota que usar los enlaces de navegación borrará las selecciones de esta column
 'editundo' => 'deshacer',
 'diff-multi' => '(No se {{PLURAL:$1|muestra una edición intermedia realizada|muestran $1 ediciones intermedias realizadas}} por {{PLURAL:$2|un usuario|$2 usuarios}})',
 'diff-multi-manyusers' => '(No se {{PLURAL:$1|muestra una edición intermedia|muestran $1 ediciones intermedias}} de {{PLURAL:$2|un usuario|$2 usuarios}})',
+'difference-missing-revision' => '{{PLURAL:$2|Un revisión| $2  revisiones}} de esta diferencia ( $1 )  no {{PLURAL:$2| ha siado encontrada|han sido encontradas}}.
+!¡ N!Esto es generalmente causado por seguir un enlace de diffs obsoletas a una página que ha sido borrada.!¡ N!Los detalles pueden encontrarse en el [{{fullurl:{{#Special:log}} / delete|page = {{FULLPAGENAMEE}}}} registro de borrado].',
 
 # Search results
 'searchresults' => 'Resultados de la búsqueda',
index f1d27e7..78ae860 100644 (file)
@@ -548,9 +548,13 @@ Vaata [[Special:Version|versiooni lehekülge]].',
 'ok' => 'Sobib',
 'pagetitle' => '$1 – {{SITENAME}}',
 'retrievedfrom' => 'Pärit leheküljelt "$1"',
-'youhavenewmessages' => 'Teile on $1 ($2).',
+'youhavenewmessages' => 'Sulle on $1 ($2).',
 'newmessageslink' => 'uusi sõnumeid',
 'newmessagesdifflink' => 'viimane muudatus',
+'youhavenewmessagesfromusers' => 'Sulle on $1 {{PLURAL:$3|ühelt|$3}} kasutajalt ($2).',
+'youhavenewmessagesmanyusers' => 'Sulle on $1 paljudelt kasutajatelt ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|uus sõnum|uusi sõnumeid}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|viimane muudatus|viimased muudatused}}',
 'youhavenewmessagesmulti' => 'Sulle on uusi sõnumeid $1',
 'editsection' => 'redigeeri',
 'editsection-brackets' => '[$1]',
@@ -575,8 +579,8 @@ Vaata [[Special:Version|versiooni lehekülge]].',
 'page-rss-feed' => '"$1" RSS-toide',
 'page-atom-feed' => '"$1" Atom-toide',
 'red-link-title' => '$1 (pole veel kirjutatud)',
-'sort-descending' => 'Sordi kahanevas järjestuses',
-'sort-ascending' => 'Sordi kasvavas järjestuses',
+'sort-descending' => 'Järjesta kahanevalt',
+'sort-ascending' => 'Järjesta kasvavalt',
 
 # Short words for each namespace, by default used in the namespace tab in monobook
 'nstab-main' => 'Artikkel',
@@ -3651,7 +3655,7 @@ Sa võid [[Special:EditWatchlist|kasutada ka harilikku tekstiredaktorit]].',
 'signature' => '[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|arutelu]])',
 
 # Core parser functions
-'unknown_extension_tag' => 'Tundmatu lisa märgend "$1".',
+'unknown_extension_tag' => 'Tundmatu lisa silt "$1".',
 'duplicate-defaultsort' => '\'\'\'Hoiatus:\'\'\' Järjestamisvõti "$2" tühistab eespool oleva järjestamisvõtme "$1".',
 
 # Special:Version
@@ -3712,7 +3716,7 @@ Pilt kuvatakse algupärases suuruses, muu fail avatakse koheselt seostuva progra
 * <span class="mw-specialpagecached">Uuendamata sisuga erileheküljed (ei pruugi enam kasutuses olla)</span>',
 'specialpages-group-maintenance' => 'Hooldusaruanded',
 'specialpages-group-other' => 'Teised erileheküljed',
-'specialpages-group-login' => 'Sisselogimine ja registreerumine',
+'specialpages-group-login' => 'Sisselogimine ja konto loomine',
 'specialpages-group-changes' => 'Viimased muudatused ja logid',
 'specialpages-group-media' => 'Failidega seonduv',
 'specialpages-group-users' => 'Kasutajad ja õigused',
@@ -3887,5 +3891,5 @@ Kui ei, kasuta allolevat lihtsat vormi. Sinu kommentaar lisatakse koos kasutajan
 'duration-millennia' => '$1 {{PLURAL:$1|aastatuhande}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 pole lubatud {{PLURAL:$3|failitüüp|failitüübid}}.  Lubatud {{PLURAL:$3|failitüüp|failitüübid}} on  $2.',
+'api-error-filetype-banned-type' => '$1 pole lubatud {{PLURAL:$4|failitüüp|failitüübid}}.  Lubatud {{PLURAL:$3|failitüüp|failitüübid}} on  $2.',
 );
index 99b3bf4..a757b91 100644 (file)
@@ -855,6 +855,7 @@ $2',
 'remembermypassword' => 'گذرواژه را (تا حداکثر $1 {{PLURAL:$1|روز|روز}}) در این رایانه به خاطر بسپار',
 'securelogin-stick-https' => 'پس از ورود به سامانه به HTTPS متصل بمان',
 'yourdomainname' => 'دامنهٔ شما:',
+'password-change-forbidden' => 'شما نمی‌توانید گذرواژه‌ها را در این ویکی تغییر دهید.',
 'externaldberror' => 'خطایی در ارتباط با پایگاه داده رخ داده‌است یا اینکه شما اجازهٔ به‌روزرسانی حساب خارجی خود را ندارید.',
 'login' => 'ورود به سامانه',
 'nav-login-createaccount' => 'ورود به سامانه / ایجاد حساب کاربری',
@@ -2557,6 +2558,8 @@ $NEWPAGE
 'rollback' => 'واگردانی ویرایش‌ها',
 'rollback_short' => 'واگردانی',
 'rollbacklink' => 'واگردانی',
+'rollbacklinkcount' => 'واگردانی $1 ویرایش',
+'rollbacklinkcount-morethan' => 'واگردانی بیش از $1 ویرایش',
 'rollbackfailed' => 'واگردانی نشد',
 'cantrollback' => 'نمی‌توان ویرایش را واگرداند؛
 آخرین مشارکت‌کننده تنها مؤلف این مقاله است.',
index 1c1c3b0..4eccacc 100644 (file)
@@ -667,6 +667,7 @@ Ilmoita tämän sivun osoite wikin [[Special:ListUsers/sysop|ylläpitäjälle]].
 'cannotdelete' => 'Sivun tai tiedoston ”$1” poisto epäonnistui.
 Joku muu on saattanut poistaa sen.',
 'cannotdelete-title' => 'Sivua $1 ei voi poistaa',
+'delete-hook-aborted' => 'Laajennuskoohdi esti muokkauksen antamatta syytä.',
 'badtitle' => 'Virheellinen otsikko',
 'badtitletext' => 'Pyytämäsi sivuotsikko oli virheellinen, tyhjä tai väärin linkitetty kieltenvälinen tai wikienvälinen linkki.',
 'perfcached' => 'Tiedot ovat välimuistista eivätkä välttämättä ole ajan tasalla. Välimuistissa on enintään {{PLURAL:$1|yksi tulos|$1 tulosta}}.',
@@ -720,6 +721,7 @@ Käyttäjätunnuksesi on luotu.
 'remembermypassword' => 'Muista minut (enintään $1 {{PLURAL:$1|päivä|päivää}})',
 'securelogin-stick-https' => 'Jatka salatun yhteyden käyttämistä sisäänkirjautumisen jälkeen',
 'yourdomainname' => 'Verkkonimi',
+'password-change-forbidden' => 'Et voi muuttaa salasanoja tässä wikissä.',
 'externaldberror' => 'Tapahtui virhe ulkoisen autentikointitietokannan käytössä tai sinulla ei ole lupaa päivittää tunnustasi.',
 'login' => 'Kirjaudu sisään',
 'nav-login-createaccount' => 'Kirjaudu sisään tai luo tunnus',
@@ -952,6 +954,10 @@ tai [{{fullurl:{{FULLPAGENAME}}|action=edit}} muokata tätä sivua]</span>.',
 'noarticletext-nopermission' => 'Tällä hetkellä tällä sivulla ei ole tekstiä.
 Voit [[Special:Search/{{PAGENAME}}|etsiä sivun nimellä]] muilta sivuilta
 tai <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hakea aiheeseen liittyviä lokeja]</span>',
+'missing-revision' => 'Muutosta #$1 sivulla "{{PAGENAME}}" ei ole olemassa.
+
+Tämä yleensä johtuu vanhentuneesta historialinkistä sivulle, joka on poistettu.
+Lisätietoja löytyy [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} poistolokista].',
 'userpage-userdoesnotexist' => 'Käyttäjätunnusta <nowiki>$1</nowiki> ei ole rekisteröity. Varmista haluatko muokata tätä sivua.',
 'userpage-userdoesnotexist-view' => 'Käyttäjätunnusta ”$1” ei ole rekisteröity.',
 'blocked-notice-logextract' => 'Tämä käyttäjä on tällä hetkellä estetty.
@@ -1239,6 +1245,10 @@ Uuden ja vanhan sivun muutoksien pitää muodostaa jatkumo – ne eivät saa men
 'editundo' => 'kumoa',
 'diff-multi' => '(Näytettyjen versioiden välissä on {{PLURAL:$1|yksi muokkaus|$1 versiota, jotka ovat {{PLURAL:$2|yhden käyttäjän tekemiä|$2 eri käyttäjän tekemiä}}}}.)',
 'diff-multi-manyusers' => '(Versioiden välissä on {{PLURAL:$1|yksi muu muokkaus|$1 muuta muokkausta, jotka on tehnyt {{PLURAL:$2|yksi käyttäjä|yli $2 eri käyttäjää}}}}.)',
+'difference-missing-revision' => '{{PLURAL:$2|Yhtä versiota|$2 versiota}} tästä diffistä ($1) {{PLURAL:$2|ei|ei}} löytynyt.
+
+Tämä johtuu yleensä seuraavasta vanhentuneeesta diffilinkistä sivulle, joka on poistettu.
+Lisätietoja löytyy [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} poistolokista].',
 
 # Search results
 'searchresults' => 'Hakutulokset',
@@ -1505,6 +1515,7 @@ Tässä satunnaisesti tuotettu arvo, jota voit käyttää: $1',
 'right-writeapi' => 'Käyttää kirjoitus-APIa',
 'right-delete' => 'Poistaa sivuja',
 'right-bigdelete' => 'Poistaa sivuja, joilla on pitkä historia',
+'right-deletelogentry' => 'Poista ja palauta tiettyjä lokimerkintöjä',
 'right-deleterevision' => 'Poistaa ja palauttaa sivujen versioita',
 'right-deletedhistory' => 'Tarkastella poistettuja versiotietoja ilman niihin liittyvää sisältöä',
 'right-deletedtext' => 'Tarkastella poistettujen sivujen tekstiä ja muutoksia poistettujen versioiden välillä',
index e50f273..8d1f27d 100644 (file)
@@ -119,7 +119,7 @@ $messages = array(
 'tog-hidepatrolled' => 'Goym eftirkannaðar rættingar í seinastu broytingum',
 'tog-newpageshidepatrolled' => 'Goym eftirkannaðar síður frá listanum yvir nýggjar síður',
 'tog-extendwatchlist' => 'Víðka eftirlitslistan fyri at vísa allar broytingar, ikki bara tær seinastu',
-'tog-usenewrc' => 'Nýt betraðar seinastu broytingar (krevur JavaScript)',
+'tog-usenewrc' => 'Bólkað broytingar eftir síðu í seinastu rættingar og eftirlitslita (krevur JavaScript)',
 'tog-numberheadings' => 'Sjálvtalmerking av yvirskrift',
 'tog-showtoolbar' => 'Vís amboðslinju í rætting',
 'tog-editondblclick' => 'Rætta síðu við at tvíklikkja (JavaScript)',
@@ -127,17 +127,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Rætta greinpart við at høgraklikkja á yvirskrift av greinparti (JavaScript)',
 'tog-showtoc' => 'Vís innihaldsyvurlit (Til greinir við meira enn trimun greinpartum)',
 'tog-rememberpassword' => 'Minst til loyniorð á hesum kaga (í mesta lagi $1 {{PLURAL:$1|dag|dagar}})',
-'tog-watchcreations' => 'Legg síður, sum eg stovni, í mítt eftirlit',
-'tog-watchdefault' => 'Vaka yvur nýggjum og broyttum greinum',
-'tog-watchmoves' => 'Legg síður afturat, sum eg havi valt afturat mínum eftirkanningarlista.',
-'tog-watchdeletion' => 'Legg síður sum eg sletti afturat mínum vaktarlista',
+'tog-watchcreations' => 'Legg síður, sum eg stovni og fílur sum eg leggi út, afturat mínum eftirlitslista',
+'tog-watchdefault' => 'Legg síður sum eg rætti afturat mínum eftirlitslista',
+'tog-watchmoves' => 'Legg síður og fílur, sum eg flyti, afturat mínum eftirlitslista',
+'tog-watchdeletion' => 'Legg síður og fílur, sum eg striki, afturat mínum eftirlitslista',
 'tog-minordefault' => 'Merk sum standard allar broytingar sum smærri',
 'tog-previewontop' => 'Vís forhondsvísning áðren rættingarkassan',
 'tog-previewonfirst' => 'Sýn forskoðan við fyrstu broyting',
 'tog-nocache' => 'Deaktivera síðu "caching" í brovsaranum',
-'tog-enotifwatchlistpages' => 'Send mær teldupost, tá ein síða á mínum eftirlitslista er broytt',
+'tog-enotifwatchlistpages' => 'Send mær teldupost, tá ein síða ella fíla á mínum eftirlitslista er broytt',
 'tog-enotifusertalkpages' => 'Send mær teldubræv, tá mín brúarasíða er broytt',
-'tog-enotifminoredits' => 'Send mær eisini teldupost viðvíkjandi smærri broytingum á síðunum',
+'tog-enotifminoredits' => 'Send mær eisini ein teldupost viðvíkjandi smærri broytingum á síðum og fílum',
 'tog-enotifrevealaddr' => 'Avdúkað mína teldupost adressu í fráboðanar teldupostum',
 'tog-shownumberswatching' => 'Vís tal av brúkarum sum fylgja við',
 'tog-oldsig' => 'Verandi undirskrift:',
@@ -376,6 +376,10 @@ Sí [[Special:Version|versjón síða]].',
 'youhavenewmessages' => 'Tú hevur $1 ($2).',
 'newmessageslink' => 'nýggj boð',
 'newmessagesdifflink' => 'seinasta broyting',
+'youhavenewmessagesfromusers' => 'Tú hevur $1 frá {{PLURAL:$3|øðrum brúkara|$3 brúkarum}} ($2).',
+'youhavenewmessagesmanyusers' => 'Tú hevur $1 frá fleiri brúkarum ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|eini nýggj boð|nýggj boð}}',
+'newmessagesdifflinkplural' => 'seinasta {{PLURAL:$1|broyting|broytingar}}',
 'youhavenewmessagesmulti' => 'Tú hevur nýggj boð á $1',
 'editsection' => 'rætta',
 'editold' => 'rætta',
@@ -471,8 +475,8 @@ Møguliga hevur onkur annar longu strikað hana.',
 'cannotdelete-title' => 'Kann ikki strika síðu "$1"',
 'badtitle' => 'Ógyldugt heiti',
 'badtitletext' => 'Umbidna síðan er ógyldugt, tómt ella skeivt tilslóðað heiti millum mál ella wikur.',
-'perfcached' => 'Fylgjandi upplýsingar eru "cached" og eru møguliga ikki dagførdir. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.',
-'perfcachedts' => 'Fylgjandi dáta er goymt, og var seinast goymt $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.',
+'perfcached' => 'Fylgjandi upplýsingar eru "fangaðir" (cached) og eru møguliga ikki dagførdir. Í mesta lagi {{PLURAL:$1|eitt úrslit er|$1 úrslit eru}} tøk í cache.',
+'perfcachedts' => 'Fylgjandi dáta er "fangað" (cached), og var seinast dagført $1. Í mesta lagi {{PLURAL:$4|eitt úrslit er|$4 úrslit eru}} tøk í cache.',
 'querypage-no-updates' => 'Tað ber í løtuni ikki til at dagføra hesa síðuna.
 Dáta higani verður í løtuni ikki endurnýggjað.',
 'wrong_wfQuery_params' => '↓ Skeiv parametir til wfQuery()<br />
@@ -594,6 +598,7 @@ verður sendur fyri nakað av fylgjandi hentleikum.',
 'invalidemailaddress' => 'T-post bústaðurin kann ikki verða góðtikin, tí hann sær út til at hava ógyldugt format.
 Vinarliga skriva t-post bústað í røttum formati ella lat handa teigin vera tóman.',
 'cannotchangeemail' => 'T-post adressur, sum eru knýttar at brúkarakontum, kunnu ikki broytast á hesi wiki.',
+'emaildisabled' => 'Henda heimasíðan kann ikki senda teldupostar.',
 'accountcreated' => 'Konto upprættað',
 'accountcreatedtext' => 'Brúkarakontan hjá $1 er nú upprættað.',
 'createaccount-title' => 'Upprætta brúkarakonto á {{SITENAME}}',
@@ -732,6 +737,21 @@ Tú kanst seta teg í samband við $1 ella ein annan [[{{MediaWiki:Grouppage-sys
 Tú kanst ikki brúka 'send t-post til henda brúkara' funktiónina, uttan so at ein galdandi t-post adressa er givin í tínum [[Special:Preferences|konto innstillingum]] og um tú ikki ert blivin sperraður frá at brúka hana.
 Tín verandi IP adressa er $3, og sperrings ID er #$5.
 Vinarliga tak allir hesir upplýsingar við í einum hvørjum fyrispurningi ið tí hevur.",
+'autoblockedtext' => 'Tín IP bústaður er blivin sjálvvirkandi sperraður, tí hann varð brúktur av einum øðrum brúkara, sum er blivin sperraður av $1.
+Viðkomandi gav hesa orsøk:
+:\'\'$2\'\'
+
+* Sperring byrjað: $8
+* Sperringin útgongur: $6
+* Intended blockee: $7
+
+Tú kanst seta teg í samband við $1 ella ein av hinum [[{{MediaWiki:Grouppage-sysop}}|administratorunum]] fyri at kjakast um sperringina.
+
+Legg til merkis, at tú kanst ikki brúka "send ein t-post til henda brúkara" funktiónina, uttan so at tú hevur ein gyldugan t-post  bústað skrásettan í tínum 
+[[Special:Preferences|brúkara ynskjum]] og at tú ikki ert blivin sperrað/ur frá at brúka hesa.
+
+IP adressan sum tú brúkar í løtuni er $3, og brúkara ID er #$5.
+Vinarliga tak allar hesar upplýsigarnar við í einum og hvørjum fyrispurningi, ið tú kanst hava.',
 'blockednoreason' => 'Ongin orsøk er givin',
 'whitelistedittext' => 'Tú mást $1 fyri at rætta hesa síðu.',
 'confirmedittext' => 'Tú mást vátta tína teldupost adressu áðrenn tú rættar síður.
@@ -785,7 +805,9 @@ Tann seinasti sperringar gerðalistin er her niðanfyri fyri ávísing:',
 Tilevnaðar .css og .js síður brúka heiti sum byrja við lítlum bókstavi, t.d.  {{ns:user}}:Foo/vector.css í mun til {{ns:user}}:Foo/Vector.css.",
 'updated' => '(Dagført)',
 'note' => "'''Viðmerking:'''",
-'previewnote' => "'''Minst til at hetta bara er ein forskoðan, sum enn ikki er goymd!'''",
+'previewnote' => "'''Minst til at hetta bara er ein forskoðan.'''
+Tínar broytingar eru ikki goymdar enn!",
+'continue-editing' => 'Halt fram við at rætta',
 'previewconflict' => 'Henda forskoðanin vísir tekstin í erva soleiðis sum hann sær út, um tú velur at goyma.',
 'session_fail_preview' => "'''Orsakað! Vit kundu ikki fullføra tínar broytingar, tí tínar sessións dáta eru horvin.'''
 Vinarliga royn aftur.
@@ -798,6 +820,7 @@ Um tað enn ikki virkar, royn so [[Special:UserLogout|rita út]] og rita so inn
 Um tað enn ikki virkar, royn so at [[Special:UserLogout|rita út]] og rita so inn aftur.",
 'edit_form_incomplete' => "'''Nakrir partar av rættingarskjalinum náddu ikki til servaran; eftirkanna tvær ferðir at tínar rættingar eru til staðar og royn so aftur.'''",
 'editing' => 'Tú rættar $1',
+'creating' => 'Upprætta $1',
 'editingsection' => 'Tú rættar $1 (partur)',
 'editingcomment' => 'Tú rættar $1 (nýtt brot)',
 'editconflict' => 'Rættingar konflikt: $1',
@@ -818,7 +841,7 @@ Við at senda arbeiði títt inn, lovar tú, at tú hevur skrivað tað, ella at
 Um tú ikki ynskir at tín skriving verður broytt miskunnarleyst, so skal tú ikki skriva nakað her.<br />
 Tú lovar okkum eisini, at tú sjálv/ur hevur skrivað hetta, ella at tú hevur avritað tað frá keldu sum er almenn ogn (public domain) ella frá líkandi fríum keldum (sí $1 fyri nærri upplýsingar). 
 '''Tú mást ikki senda tilfar inn, sum er vart av upphavsrætti, uttan so at tú hevur fingið loyvi til tað!'''",
-'longpageerror' => "'''Feilur: Teksturin sum tú hevur sent inn er $1 kilobytes (kB) langur, sum er størri enn mest loyvda sum er $2 kilobytes.'''
+'longpageerror' => "'''Feilur: Teksturin sum tú hevur sent inn er {{PLURAL:$1|eitt kilobyte|$1 kilobytes}} langur, sum er longri enn mest loyvda, sum er  {{PLURAL:$2|eitt kilobyte|$2 kilobytes}}.'''
 Teksturin kann tí ikki verða goymdur.",
 'protectedpagewarning' => "'''Ávaring: Henda síðan er friðað, so at einans brúkarar við umboðsstjóra heimildum kunnu broyta hana.'''
 Tann seinasta logg inn er goymt niðanfyri fyri ávísing:",
@@ -952,8 +975,12 @@ $1",
 'deletedhist' => 'Strikingar søga',
 'revdelete-hide-current' => 'Tað er hendur ein feilur tá luturin skuldi fjalast, luturin er dagfestur $2, kl. $1: Hetta er nýggjast versjónin.
 Hon kann ikki fjalast.',
+'revdelete-show-no-access' => 'Feilur tá hesin lutur dagfestur $1 klokkan $2 skuldi vísast:Hesin lutur er blivin markeraður sum "avmarkaður".
+Tú hevur ikki atgongd til hann.',
+'revdelete-otherreason' => 'Onnur orsøk',
 'revdelete-reasonotherlist' => 'Onnur orsøk',
 'revdelete-edit-reasonlist' => 'Rætta strikingar orsøkir',
+'revdelete-offender' => 'Høvundurin av hesi endurskoðan:',
 
 # History merging
 'mergehistory-from' => 'Keldusíða:',
@@ -973,7 +1000,7 @@ Hon kann ikki fjalast.',
 'mergelogpagetext' => 'Niðanfyri er ein listi við teimum nýggjastu samanflættingunum av einari síðu søgu til eina aðra.',
 
 # Diffs
-'history-title' => 'Versjónssøgan hjá "$1"',
+'history-title' => 'Eftirlitssøgan hjá "$1"',
 'difference-multipage' => '(Munur millum síður)',
 'lineno' => 'Linja $1:',
 'compareselectedversions' => 'Bera saman valdar útgávur',
@@ -1066,6 +1093,7 @@ Legg til merkis, at teirra innihaldsyvirlit av {{SITENAME}} kann vera gamalt og
 'prefs-skin' => 'Hamur',
 'skin-preview' => 'Forskoðan',
 'prefs-datetime' => 'Dato og tíð',
+'prefs-user-pages' => 'Brúkarasíður',
 'prefs-personal' => 'Brúkaradáta',
 'prefs-rc' => 'Nýkomnar broytingar og stubbaskoðan',
 'prefs-watchlist' => 'Eftirlit',
@@ -1073,6 +1101,7 @@ Legg til merkis, at teirra innihaldsyvirlit av {{SITENAME}} kann vera gamalt og
 '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-edits-max' => 'Í mesta lagi: 1000',
+'prefs-watchlist-token' => 'Lykil til eftirlitslistan:',
 'prefs-misc' => 'Ymiskar innstillingar',
 'prefs-resetpass' => 'Broyt loyniorð',
 'prefs-changeemail' => 'Broyt t-post adressu',
@@ -1127,9 +1156,13 @@ Legg til merkis, at teirra innihaldsyvirlit av {{SITENAME}} kann vera gamalt og
 'yourrealname' => 'Títt navn*:',
 'yourlanguage' => 'Mál til brúkaraflatu:',
 'yournick' => 'Nýggj undirskrift:',
+'badsiglength' => 'Tín undirskrift er ov long. 
+Hon má ikki hava meira enn $1 {{PLURAL:$1|tekn|tekn}}',
 'yourgender' => 'Kyn:',
+'gender-unknown' => 'Ikki upplýst',
 'gender-male' => 'Maður',
 'gender-female' => 'Kvinna',
+'prefs-help-gender' => 'Tú avgerð sjálv/ur: Tað verður brúkt til at fáa kynsrættan tekst á nøkrum málum. Henda kunning verður almenn.',
 'email' => 'T-post',
 'prefs-help-realname' => 'Veruligt navn er valfrítt.
 Um tú velur at skriva tað her, so verður tað nýtt til at geva tær æruna fyri títt arbeiði.',
@@ -1138,7 +1171,9 @@ Um tú velur at skriva tað her, so verður tað nýtt til at geva tær æruna f
 Tín t-post adressa verður ikki avdúkað, tá aðrir brúkarir seta seg í samband við teg.',
 'prefs-help-email-required' => 'T-post adressa er kravd.',
 'prefs-info' => 'Grundleggjandi kunning',
+'prefs-i18n' => 'Altjóðagerð',
 'prefs-signature' => 'Undirskrift',
+'prefs-timeoffset' => 'Tíðarmunur',
 'prefs-advancedediting' => 'Víðkaðir møguleikar',
 'prefs-advancedrc' => 'Víðkaðir møguleikar',
 'prefs-advancedrendering' => 'Víðkaðir møguleikar',
@@ -1162,8 +1197,14 @@ Tín t-post adressa verður ikki avdúkað, tá aðrir brúkarir seta seg í sam
 'userrights-editusergroup' => 'Rætta brúkarabólkar',
 'saveusergroups' => 'Goym brúkaraflokk',
 'userrights-groupsmember' => 'Limur í:',
+'userrights-groupsmember-auto' => 'Óbeinleiðis limur í:',
+'userrights-groups-help' => 'Tú kanst broyta bólkalimaskap hjá hesum limi: 
+* Ein krossaður kassi merkir, at hesin brúkari er limur í tí bólkinum. 
+* Ein kassi sum ikki er krossaður (tjekk merktur) merkir, at brúkarin ikki er limur í tí bólkinum. 
+* Ein * merkir, at tú kanst ikki taka bólkin burtur, tá tú fyrst hevur sett hann inn og mótsatt.',
 'userrights-reason' => 'Orsøk:',
 'userrights-no-interwiki' => 'Tú hevur ikki loyvi til at rætta brúkara rættindi á øðrum wikium.',
+'userrights-nodatabase' => 'Dátugrunnurin $1 er ikki til ella er hann ikki lokalur.',
 'userrights-notallowed' => 'Tín konto hevur ikki loyvi til at seta ella taka burtur brúkara rættindi.',
 'userrights-changeable-col' => 'Bólkar sum tú kanst broyta',
 'userrights-unchangeable-col' => 'Bólkar, ið tú ikki kanst broyta',
@@ -1208,8 +1249,12 @@ Tín t-post adressa verður ikki avdúkað, tá aðrir brúkarir seta seg í sam
 'right-upload_by_url' => 'Legg fílur upp frá einum URL',
 'right-delete' => 'Strika síður',
 'right-bigdelete' => 'Strika síður við nógvum versjónum',
+'right-browsearchive' => 'Leita í strikaðum síðum',
 'right-block' => 'Nokta øðrum brúkarum at rætta (blokka)',
 'right-hideuser' => 'Sperra eitt brúkaranavn og goyma tað burtur fyri almenninginum',
+'right-unblockself' => 'Taka burtur sperring av sær sjálvum',
+'right-protect' => 'Broyt verjustøður og rætta vardar síður',
+'right-editprotected' => 'Rætta vardar síður (uttan niðurarvaða verju)',
 'right-import' => 'Innflyt síður frá øðrum wikium',
 'right-patrol' => 'Marka broytingar hjá øðrum sum eftirkannaðar',
 'right-unwatchedpages' => 'Sí lista við síðum sum ikki eru eftiransaðar',
@@ -1217,8 +1262,13 @@ Tín t-post adressa verður ikki avdúkað, tá aðrir brúkarir seta seg í sam
 'right-userrights' => 'Rætta øll brúkaraloyvir',
 'right-userrights-interwiki' => 'Broyt brúkara rættindi hjá brúkarum á øðrum wikium',
 'right-sendemail' => 'Send t-post til aðrir brúkarar',
+'right-passwordreset' => 'Sí teldupostar til nullstilling av loyniorði',
 
 # User rights log
+'rightslog' => 'Rættindaloggur',
+'rightslogtext' => 'Hetta er ein loggur sum vísir broytingar í brúkararættindum.',
+'rightslogentry' => 'broyttar bólka limaskap fyri $1 frá $2 til $3',
+'rightslogentry-autopromote' => 'varð sjálvvirkandi fluttur upp frá $2 til $3',
 'rightsnone' => '(ongin)',
 
 # Associated actions - in the sentence "You do not have permission to X"
@@ -1230,6 +1280,7 @@ Tín t-post adressa verður ikki avdúkað, tá aðrir brúkarir seta seg í sam
 'action-minoredit' => 'marka hesa rætting sum lítla',
 'action-move' => 'flyt hesa síðu',
 'action-move-subpages' => 'flyt hesa síðu og undirsíður hennara',
+'action-move-rootuserpages' => 'flyt høvuðs brúkarasíður',
 'action-movefile' => 'flyt hesa fílu',
 'action-upload' => 'send hesa fílu upp',
 'action-delete' => 'Strika hesa síðu',
@@ -1275,6 +1326,7 @@ Tín t-post adressa verður ikki avdúkað, tá aðrir brúkarir seta seg í sam
 'newsectionsummary' => '/* $1 */ nýtt innlegg',
 'rc-enhanced-expand' => 'Vís smálutir (krevur JavaScript)',
 'rc-enhanced-hide' => 'Goym smálutir',
+'rc-old-title' => 'upprunaliga stovnað sum "$1"',
 
 # Recent changes linked
 'recentchangeslinked' => 'Viðkomandi broytingar',
@@ -1290,6 +1342,8 @@ Síður á [[Special:Watchlist|tínum eftiransingarlista]] eru skrivaðar við '
 # Upload
 'upload' => 'Legg fílu upp',
 'uploadbtn' => 'Legg fílu upp',
+'reuploaddesc' => 'Angra uppløðu og far aftur til upload formin',
+'upload-tryagain' => 'Goym broytta fílu frágreiðing',
 'uploadnologin' => 'Ikki ritað inn',
 'uploadnologintext' => 'Tú mást hava [[Special:UserLogin|ritað inn]]
 fyri at leggja fílur upp.',
@@ -1328,6 +1382,9 @@ Vinarliga gev fíluni nýtt navn og royn at senda hana upp (uploada) enn einafer
 'large-file' => 'Tað verður viðmælt, at fílur ikki eru størri enn $1;
 henda fílin er $2.',
 'largefileserver' => 'Henda fílan er størri enn servarin er innstillaður til at loyva.',
+'file-deleted-duplicate' => 'Ein fíla, sum er líka sum henda ([[:$1]]) er fyrr blivin strikað.
+Tú eigur at kanna eftir strikingarsøguna hjá hesi fílu, áðrenn tú heldur áframm við at leggja hana út enn einaferð.',
+'uploadwarning' => 'Ávaring',
 'savefile' => 'Goym fílu',
 'uploadedimage' => 'sent "[[$1]]" upp',
 'sourcefilename' => 'Kelda fílunavn:',
@@ -1345,6 +1402,7 @@ Tú eigur at eftirkanna $1 áðrenn tú heldur á við at upplóta fíluna enn e
 'upload-unknown-size' => 'Ókend stødd',
 
 # File backend
+'backend-fail-notexists' => 'Fílan $1 er ikki til.',
 'backend-fail-store' => 'Kundi ikki goyma fílu $1 á $2.',
 'backend-fail-copy' => 'Kundi ikki avrita fílu $1 til $2.',
 'backend-fail-move' => 'Kundi ikki flyta fílu $1 til $2.',
@@ -1352,26 +1410,55 @@ Tú eigur at eftirkanna $1 áðrenn tú heldur á við at upplóta fíluna enn e
 'backend-fail-writetemp' => 'Kundi ikki skriva til fyribils fílu.',
 'backend-fail-closetemp' => 'Kundi ikki aftur fyribils fílu.',
 'backend-fail-read' => 'Kundi ikki lesa fílu $1.',
-'backend-fail-create' => 'Kundi ikki skapa fílu $1.',
+'backend-fail-create' => 'Kundi ikki skriva fílu $1.',
 
 # Lock manager
 'lockmanager-notlocked' => 'Kundi ikki lata upp "$1"; hon er ikki stongd.',
 'lockmanager-fail-closelock' => 'Kundi ikki lata aftur lás fílu fyri "$1".',
 'lockmanager-fail-deletelock' => 'Kundi ikki sletta lás fílu fyri "$1".',
+'lockmanager-fail-acquirelock' => 'Kundi ikki fáa lás til "$1".',
+'lockmanager-fail-openlock' => 'Kundi ikki læsa upp fíluna til: "$1".',
+'lockmanager-fail-releaselock' => 'Kundi ikki læsa upp læsingina fyri: "$1".',
 
 # img_auth script messages
+'img-auth-accessdenied' => 'Atgongd noktað',
+'img-auth-nologinnWL' => 'Tú ert ikki ritað/ur inn, og "$1" er ikki á hvítalista.',
+'img-auth-nofile' => 'Fílan "$1" er ikki til',
+'img-auth-isdir' => 'Tú roynir at fáa atgongd til mappuna "$1".
+Bert fílu atgongd er loyvd.',
+'img-auth-streaming' => 'Sendir "$1".',
 'img-auth-noread' => 'Brúkarin hevur ikki rættindi til at lesa "$1".',
 'img-auth-bad-query-string' => "URL'urin hevur ein ikki galdandi fyrispurning strong.",
 
 # HTTP errors
 'http-invalid-url' => 'Ógildug URL (internetadressa): $1',
+'http-invalid-scheme' => 'URLar av slagnum "$1" verða ikki stuðlaðir.',
+'http-request-error' => 'HTTP fyrispurningurin riggaði ikki av ókendum orsøkum.',
+'http-read-error' => 'HTTP lesifeilur.',
+'http-timed-out' => 'HTTP fyrispurningurin tók ov langa tíð.',
+'http-curl-error' => 'Feilur meðan vit heintaðu URL: $1',
+'http-host-unreachable' => 'Internetadressan er ikki atkomulig.',
+'http-bad-status' => 'Tað hendi ein feilur undir viðgerðini av HTTP fyrispurnininum: $1 $2',
+
+# Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
+'upload-curl-error6' => 'URLurin er ikki atkomuligur',
+'upload-curl-error6-text' => "URL'urin sum tú skrivaði er ikki atkomuligur.
+Vinarliga dupult-eftirkannað at URL er rættur og at heimasíðan koyrir.",
+'upload-curl-error28' => 'Tað gekk ov long tíð við uppload',
+'upload-curl-error28-text' => 'Heimasíðan tók ov langa tíð at svara. 
+Vinarliga kanna eftir um síðan koyrir (er online), bíða eina lítla løtu og royn so aftur.
+Tú kanst eisini royna aftur, tá tað ikki eru so nógv í gongd her í senn.',
 
 'license' => 'Lisensur:',
 'license-header' => 'Lisensur',
 'nolicense' => 'Onki valt',
 'license-nopreview' => '(Fyrr ikki tøkt)',
+'upload_source_url' => '(ein galdandi, alment atkomuligan URL)',
+'upload_source_file' => '(ein fíla á tínari teldu)',
 
 # Special:ListFiles
+'listfiles_search_for' => 'Leita eftir miðla navni:',
+'imgfile' => 'fíla',
 'listfiles' => 'Myndalisti',
 'listfiles_thumb' => 'Lítli mynd',
 'listfiles_date' => 'Dagur',
@@ -1400,10 +1487,15 @@ Tú eigur at eftirkanna $1 áðrenn tú heldur á við at upplóta fíluna enn e
 'filehist-missing' => 'Fíla væntar',
 'imagelinks' => 'Nýtsla av fílu',
 'linkstoimage' => 'Fylgjandi {{PLURAL:$1|síða slóðar|$1 síður slóða}} til hesa fílu:',
+'linkstoimage-more' => 'Meira enn $1 {{PLURAL:$1|síða slóðar|síður slóða}} til hesa fílu.
+Hesin listin vísir {{PLURAL:$1|fyrstu síðu slóð|firstu $1 síðu slóðir}} bert til hesa fílu.
+Ein [[Special:WhatLinksHere/$2|fullur listi]] er tøkur.',
 'nolinkstoimage' => 'Ongar síður slóða til hesa myndina.',
 'morelinkstoimage' => 'Sí [[Special:WhatLinksHere/$1|fleiri leinkjur]] til hesa fílu.',
+'linkstoimage-redirect' => '$1 (fílu víðaristilling) $2',
 'sharedupload-desc-here' => 'Henda fíla er frá $1 og kann verða brúka í øðrum verkætlanum.
 Frágreiðingin á [$2 fílu frágreiðingar síðu] er víst her niðanfyri.',
+'shared-repo-from' => 'frá $1',
 
 # File deletion
 'filedelete' => 'Strika $1',
@@ -1494,6 +1586,9 @@ Víðaristilling verður nú gjørd til [[$2]].',
 'deadendpages' => 'Gøtubotnssíður',
 'protectedpages' => 'Friðaðar síður',
 'listusers' => 'Brúkaralisti',
+'listusers-editsonly' => 'Vís bara brúkarar sum hava gjørt rættingar',
+'listusers-creationsort' => 'Bólkað eftir stovningardegnum',
+'usereditcount' => '$1 {{PLURAL:$1|rætting|rættingar}}',
 'usercreated' => '{{GENDER:$3|Upprættað}} hin $1 kl. $2',
 'newpages' => 'Nýggjar síður',
 'newpages-username' => 'Brúkaranavn:',
@@ -1536,6 +1631,11 @@ Tú kanst avmarka sýningina við at velja slag av loggi, brúkaranavn (sum er f
 [[Special:UnusedCategories|Ikki brúktir bólkar]] eru ikki vístar her.
 Sí eisini [[Special:WantedCategories|ynsktir bólkar]].',
 
+# Special:DeletedContributions
+'deletedcontributions' => 'Slettaði brúkaraíkøst',
+'deletedcontributions-title' => 'Slettaði brúkaraíkøst',
+'sp-deletedcontributions-contribs' => 'íkøst',
+
 # Special:LinkSearch
 'linksearch-ns' => 'Navnarúm:',
 'linksearch-ok' => 'Leita',
@@ -1546,24 +1646,49 @@ Sí eisini [[Special:WantedCategories|ynsktir bólkar]].',
 'listusers-submit' => 'Sýna',
 'listusers-noresult' => 'Ongin brúkari var funnin.',
 
+# Special:ActiveUsers
+'activeusers-hidebots' => 'Fjal bottar',
+'activeusers-hidesysops' => 'Fjal umboðsstjórar (administratorar)',
+'activeusers-noresult' => 'Ongir brúkarar funnir.',
+
 # Special:Log/newusers
 'newuserlogpage' => 'Brúkara logg',
+'newuserlogpagetext' => 'Hetta er ein listi yvir seinast stovnaðu brúkarar.',
 
 # Special:ListGroupRights
+'listgrouprights' => 'Brúkara bólka rættindi',
+'listgrouprights-summary' => 'Henda síða vísir ein lista av brúkarabólkum, sum eru útgreinaðir á hesi wiki og rættindini hjá teimum einstøku bólkunum.
+Møguliga er [[{{MediaWiki:Listgrouprights-helppage}}|meira kunning]] um einstøk rættindi.',
+'listgrouprights-key' => '* <span class="listgrouprights-granted">Givin rættindi</span>
+* <span class="listgrouprights-revoked">Frátikin rættindi</span>',
+'listgrouprights-group' => 'Bólkur',
+'listgrouprights-rights' => 'Rættindi',
+'listgrouprights-helppage' => 'Help:Bólka rættindi',
 'listgrouprights-members' => '(limalisti)',
+'listgrouprights-removegroup' => 'Tak burtur {{PLURAL:$2|bólk|bólkar}}: $1',
+'listgrouprights-addgroup-all' => 'Legg til allir bólkar',
+'listgrouprights-removegroup-all' => 'Tak burtur allir bólkar',
+'listgrouprights-addgroup-self' => 'Legg {{PLURAL:$2|bólk|bólkar}} til tína egnu konto: $1',
+'listgrouprights-removegroup-self' => 'Tak burtur {{PLURAL:$2|bólk|bólkar}} frá egnari konto: $1',
+'listgrouprights-addgroup-self-all' => 'Legg allir bólkar til egna konto',
+'listgrouprights-removegroup-self-all' => 'Tak burtur allir bólkar frá egnari konto',
 
 # E-mail user
+'mailnologin' => 'Ongin móttakara bústaður',
 'mailnologintext' => 'Tú mást hava [[Special:UserLogin|ritað inn]]
 og hava virkandi teldupostadressu í [[Special:Preferences|innstillingum]] tínum
 fyri at senda teldupost til aðrar brúkarar.',
 'emailuser' => 'Send t-post til brúkara',
 'emailpage' => 'Send t-post til brúkara',
+'emailpagetext' => 'Tú kanst brúka skjalið niðanfyri til at senda ein teldupost til henda brúkaran.
+Teldupost adressan sum tú skrivaði í [[Special:Preferences|tíni brúkara ynskir]] kemur síðan fram sum "Frá" adressan í teldupostinum, soleiðis at móttakarin kann svara beinleiðis til tín.',
 'defemailsubject' => '{{SITENAME}} t-postur frá brúkara $1',
 'usermaildisabled' => 'Brúkara t-postur er óvirkin',
 'usermaildisabledtext' => 'Tú kanst ikki senda teldupost til aðrir brúkarar á hesi wiki',
 'noemailtitle' => 'Ongin t-post adressa',
 'noemailtext' => 'Hesin brúkarin hevur ikki upplýst eina gylduga t-post-adressu.',
 'nowikiemailtitle' => 'Ongin t-postur er loyvdur',
+'emailusername' => 'Brúkaranavn:',
 'emailfrom' => 'Frá:',
 'emailto' => 'Til:',
 'emailsubject' => 'Evni:',
@@ -1611,7 +1736,10 @@ Vilt tú flyta síðuna undan tínum eftirliti, kanst tú trýsta á \"Strika ef
 'confirm' => 'Vátta',
 'excontent' => "innihald var: '$1'",
 'excontentauthor' => "innihaldið var: '$1' (og einasti rithøvundur var '[[Special:Contributions/$2|$2]]')",
+'exbeforeblank' => 'innihaldið áðrenn síðan varð tømd var: "$1"',
 'exblank' => 'síðan var tóm',
+'delete-confirm' => 'Strikað "$1"',
+'delete-legend' => 'Strikað',
 'historywarning' => "'''Ávaring:''' Síðan, ið tú ert í gongd við at strika, hevur eina søgu við umleið $1 {{PLURAL:$1|broyting|broytingum}}:",
 'confirmdeletetext' => 'Tú ert í gongd við endaliga at strika ein a síðu
 ella mynd saman við allari søgu úr dátugrunninum.
@@ -1623,14 +1751,29 @@ avleiðingarnar og at tú gert tað í tráð við
 'deletedtext' => '"$1" er nú strikað.
 Sí $2 fyri fulla skráseting av strikingum.',
 'dellogpage' => 'Striku logg',
+'dellogpagetext' => 'Niðanfyri síggjast tær nýggjastu strikingarnar.',
 'deletionlog' => 'striku logg',
+'reverted' => 'Aftur til eina eldri verjsón',
 'deletecomment' => 'Orsøk:',
+'deleteotherreason' => 'Onnur orsøk:',
+'deletereasonotherlist' => 'Onnur orsøk',
+'deletereason-dropdown' => '*Vanligar orsøkir til striking
+** Umbøn frá høvunda
+** Brot á upphavsrættin
+** Herverk (Vandalisma)',
+'delete-edit-reasonlist' => 'Rætta orsøkir til striking',
+'delete-toobig' => 'Henda síðan hevur eina langa rættingar søgu, meira enn $1 {{PLURAL:$1|versjón|versjónir}}. 
+Striking av slíkum síðum er avmarkað fyri at forða fyri at onkur av óvart kemur til at forstýra {{SITENAME}}.',
 
 # Rollback
 'rollback' => 'Rulla broytingar aftur',
 'rollback_short' => 'Rulla aftur',
 'rollbacklink' => 'afturrulling',
 'rollbackfailed' => 'Afturrulling miseydnað',
+'revertpage' => 'Tók burtur rættingar hjá [[Special:Contributions/$2|$2]] ([[User talk:$2|kjak]]) til seinastu versjón hjá [[User:$1|$1]]',
+'revertpage-nouser' => 'Tók burtur rættingar hjá (brúkaranavn tikið vekk) til seinastu versjón hjá [[User:$1|$1]]',
+'rollback-success' => 'Tók burtur rættingar hjá $1;
+broytti tað aftur til seinastu versjón hjá $2.',
 
 # Protect
 'protectlogpage' => 'Friðingarbók',
index cd68581..90dbcac 100644 (file)
@@ -9,9 +9,12 @@
  *
  * @author Agzennay
  * @author Arkanosis
+ * @author Boniface
+ * @author Brunoperel
  * @author Cedric31
  * @author ChrisPtDe
  * @author Coyau
+ * @author Cquoi
  * @author Crochet.david
  * @author Csisc
  * @author Damouns
@@ -22,6 +25,7 @@
  * @author Dodoïste
  * @author Elfix
  * @author Enzoreg
+ * @author Erkethan
  * @author Esbardu
  * @author Fryed-peach
  * @author Giro720
@@ -62,6 +66,7 @@
  * @author Sherbrooke
  * @author Skalman
  * @author The Evil IP address
+ * @author Tititou36
  * @author TouzaxA
  * @author Tpt
  * @author Urhixidur
@@ -629,6 +634,10 @@ $1',
 'youhavenewmessages' => 'Vous avez $1 ($2).',
 'newmessageslink' => 'de nouveaux messages',
 'newmessagesdifflink' => 'dernière modification',
+'youhavenewmessagesfromusers' => "Vous avez  $1  d'{{PLURAL:$3| un autre utilisateur|$3 autres utilisateurs}} ( $2 ).",
+'youhavenewmessagesmanyusers' => 'Vous avez $1 de nombreux utilisateurs ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|un message|nouveaux messages}}',
+'newmessagesdifflinkplural' => 'dernières {{PLURAL:$1|modification|modifications}}',
 'youhavenewmessagesmulti' => 'Vous avez de nouveaux messages sur $1.',
 'editsection' => 'modifier',
 'editold' => 'modifier',
@@ -933,7 +942,7 @@ Mot de passe temporaire : $2',
 'link_tip' => 'Lien interne',
 'extlink_sample' => 'http://www.example.com titre du lien',
 'extlink_tip' => 'Lien externe (n’oubliez pas le préfixe http://)',
-'headline_sample' => 'Texte de sous-titre',
+'headline_sample' => 'Texte dtitre',
 'headline_tip' => 'Sous-titre niveau 2',
 'nowiki_sample' => 'Entrez le texte non formaté ici',
 'nowiki_tip' => 'Ignorer la syntaxe wiki',
@@ -1142,6 +1151,7 @@ Il devrait y avoir moins de $2 appel{{PLURAL:$2||s}}, alors qu’il y en a maint
 'expansion-depth-exceeded-warning' => "Page dépassant la profondeur d'expansion",
 'parser-unstrip-loop-warning' => 'Boucle non démontable détectée',
 'parser-unstrip-recursion-limit' => 'Limite de récursion non démontable dépassée ($1)',
+'converter-manual-rule-error' => 'Erreur détectée dans la règle manuelle de conversion de langue',
 
 # "Undo" feature
 'undo-success' => 'Cette modification va être défaite. Veuillez vérifier les modifications ci-dessous, puis publier si c’est bien ce que vous voulez faire.',
@@ -1407,7 +1417,7 @@ Essayez en utilisant le préfixe ''all:'' pour rechercher dans tout le contenu (
 
 # Preferences page
 'preferences' => 'Préférences',
-'mypreferences' => 'Préférences',
+'mypreferences' => 'Mes préférences',
 'prefs-edits' => 'Nombre de modifications :',
 'prefsnologin' => 'Non connecté',
 'prefsnologintext' => 'Vous devez être <span class="plainlinks">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}} connecté]</span> pour modifier vos préférences d’utilisateur.',
@@ -1997,7 +2007,7 @@ Quand elle est filtrée par utilisateur, seuls les fichiers dont la version la p
 'filehist-revert' => 'rétablir',
 'filehist-current' => 'actuel',
 'filehist-datetime' => 'Date et heure',
-'filehist-thumb' => 'Miniature',
+'filehist-thumb' => 'Vignette',
 'filehist-thumbtext' => 'Vignette pour la version du $1',
 'filehist-nothumb' => 'Pas de miniature',
 'filehist-user' => 'Utilisateur',
@@ -2017,7 +2027,7 @@ Une [[Special:WhatLinksHere/$2|liste complète]] est disponible.',
 'sharedupload' => 'Ce fichier provient de : $1. Il peut être utilisé par d’autres projets.',
 'sharedupload-desc-there' => 'Ce fichier provient de : $1. Il peut être utilisé par d’autres projets.
 Veuillez consulter [$2 sa page de description] pour plus d’informations.',
-'sharedupload-desc-here' => 'Ce fichier provient de $1. Il peut être utilisé par d’autres projets.
+'sharedupload-desc-here' => 'Ce fichier provient de $1. Il peut être utilisé par d’autres projets.
 Sa description sur sa [$2 page de description] est affichée ci-dessous.',
 'sharedupload-desc-edit' => 'Ce fichier provient de : $1. Il peut être utilisé par d’autres projets.
 Vous voulez peut-être modifier la description sur sa [$2 page de description].',
index 3940907..34374b5 100644 (file)
@@ -710,7 +710,7 @@ La rêson balyêye est « ''$2'' ».",
 'logouttext' => "'''Ora vos éte dèbranchiê{{GENDER:||ye|(ye)}}.'''
 
 Vos pouede continuar a empleyér {{SITENAME}} de façon anonima ou ben [[Special:UserLogin|vos tornar branchiér]] desot lo mémo nom ou un ôtro.
-Notâd que doux-três pâges pôvont étre oncor montrâyes coment se vos érâd adés branchiê{{GENDER:||ye|(ye)}}, tant que vos èfaciéd lo cacho de voutron navigator.",
+Notâd qu’y at des pâges que pôvont étre oncor fêtes vêre coment se vos érâd adés branchiê{{GENDER:||ye|(ye)}}, tant que vos èfaciéd lo cacho de voutron navigator.",
 'welcomecreation' => '== Benvegnua, $1 ! ==
 Voutron compto utilisator est étâ fêt.
 Oubliâd pas de pèrsonalisar voutres [[Special:Preferences|prèferences dessus {{SITENAME}}]].',
index c97bf3a..68a5ba7 100644 (file)
@@ -666,6 +666,7 @@ Dh'fhaoidte nach urrainn dhut am feart \"Cuir post-d dhan chleachdaiche seo\" a
 
 'S e \$3 an seòladh IP làithreach agus agus 's e #\$5 ID a' bhacaidh.
 Thoir iomradh air a' mhion-fhiosrachadh gu h-àrd ma chuireas tu ceist sam bith mu dhèidhinn.",
+'blockednoreason' => 'cha deach adhbhar a shònrachadh',
 'loginreqlink' => 'log a-steach',
 'accmailtitle' => 'Facal-faire air a chur.',
 'accmailtext' => "Chaidh facal-faire a chruthachadh air thuaiream airson [[User talk:$1|$1]] 's a chur gu $2.
index c647163..75cb1ab 100644 (file)
@@ -487,6 +487,10 @@ $1',
 'youhavenewmessages' => 'Ten $1 ($2).',
 'newmessageslink' => 'mensaxes novas',
 'newmessagesdifflink' => 'diferenzas coa revisión anterior',
+'youhavenewmessagesfromusers' => 'Ten $1 {{PLURAL:$3|doutro usuario|de $3 usuarios}} ($2).',
+'youhavenewmessagesmanyusers' => 'Ten $1 de moitos usuarios ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|unha mensaxe nova|$1 mensaxes novas}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|última modificación|últimas modificacións}}',
 'youhavenewmessagesmulti' => 'Ten mensaxes novas en $1',
 'editsection' => 'editar',
 'editold' => 'editar',
index 4aa14ec..b7249cb 100644 (file)
@@ -162,17 +162,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'વિભાગના મથાળાં ને રાઇટ ક્લિક દ્વારા ફેરફાર કરવાની રીત અપનાવો. (જાવાસ્ક્રિપ્ટ જરૂરી)',
 'tog-showtoc' => 'અનુક્રમણિકા દર્શાવો (૩થી વધુ પેટા-મથાળા વાળા લેખો માટે)',
 'tog-rememberpassword' => 'આ કમ્પ્યૂટર પર મારી લોગ-ઇન વિગતો યાદ રાખો (મહત્તમ $1 {{PLURAL:$1|દિવસ|દિવસ}} માટે)',
-'tog-watchcreations' => 'મà«\87àª\82 àª²àª\96à«\87લા àª¨àªµàª¾ àª²à«\87àª\96à«\8b મારી ધ્યાનસૂચિમાં ઉમેરો',
-'tog-watchdefault' => 'હું ફેરફાર કરૂં તે પાના મારી ધ્યાનસૂચિમાં ઉમેરો',
-'tog-watchmoves' => 'હà«\81àª\82 àª\9cà«\87નà«\81àª\82 àª¨àª¾àª® àª¬àª¦àª²à«\81àª\82 àª¤à«\87 àªªàª¾àª¨àª¾ મારી ધ્યાનસૂચિમાં ઉમેરો',
-'tog-watchdeletion' => 'હà«\81àª\82 àª¹àª\9fાવà«\81àª\82 àª¤à«\87 àªªàª¾àª¨àª¾ મારી ધ્યાનસૂચિમાં ઉમેરો',
+'tog-watchcreations' => 'મà«\87àª\82 àª\89મà«\87રà«\87લા àªªàª¾àª¨àª¾àª\82àª\93 àª\85નà«\87 àª\85પલà«\8bડ àª\95રà«\87લ àª«àª¾àª\87લà«\8dસ મારી ધ્યાનસૂચિમાં ઉમેરો',
+'tog-watchdefault' => 'હું ફેરફાર કરૂં તે પાનાં અને ફાઇલ્સ મારી ધ્યાનસૂચિમાં ઉમેરો',
+'tog-watchmoves' => 'હà«\81àª\82 àª\96સà«\87ડà«\81àª\82 àª¤à«\87 àªªàª¾àª¨àª¾àª\82 àª\85નà«\87 àª«àª¾àª\87લà«\8dસ મારી ધ્યાનસૂચિમાં ઉમેરો',
+'tog-watchdeletion' => 'હà«\81àª\82 àª¦à«\82ર àª\95રà«\81àª\82 àª¤à«\87 àªªàª¾àª¨àª¾àª\82 àª\85નà«\87 àª«àª¾àª\87લà«\8dસ મારી ધ્યાનસૂચિમાં ઉમેરો',
 'tog-minordefault' => 'બધા નવા ફેરફારો નાના તરીકે માર્ક કરો.',
 'tog-previewontop' => 'એડીટ બોક્સ પહેલાં પ્રિવ્યુ બતાવો.',
 'tog-previewonfirst' => 'પ્રથમ ફેરફાર વખતે પ્રિવ્યુ બતાવો.',
 'tog-nocache' => 'બ્રાઉઝરનું પેજ કેશિંગ અક્રિય કરો',
-'tog-enotifwatchlistpages' => 'મારà«\80 àª§à«\8dયાનસà«\82àª\9aિમાàª\82નાàª\82 àªªàª¾àª¨àª¾માં ફેરફાર થાય ત્યારે મને ઇ-મેલ મોકલો',
+'tog-enotifwatchlistpages' => 'મારà«\80 àª§à«\8dયાનસà«\82àª\9aિમાàª\82નà«\81àª\82 àªªàª¾àª¨à«\81 àª\85નà«\87 àª«àª¾àª\87લમાં ફેરફાર થાય ત્યારે મને ઇ-મેલ મોકલો',
 'tog-enotifusertalkpages' => 'મારી ચર્ચાનાં પાનામાં ફેરફાર થાય ત્યારે મને ઇ-મેલ મોકલો',
-'tog-enotifminoredits' => 'પાનામાàª\82 àª¨àª¾àª¨àª¾àª\82 àª«à«\87રફાર àª¥àª¾àª¯ àª¤à«\8dયારà«\87 પણ મને ઇ-મેલ મોકલો',
+'tog-enotifminoredits' => 'પાનાàª\82 àª\85નà«\87ફાàª\87લà«\8dસમાàª\82 àª¨àª¾àª¨àª¾àª\82 àª«à«\87રફાર àª¥àª¾àª¯ àª¤à«\8b પણ મને ઇ-મેલ મોકલો',
 'tog-enotifrevealaddr' => 'નોટીફીકેશનના ઇમેલમાં મારૂ ઇમેલ એડ્રેસ બતાવો',
 'tog-shownumberswatching' => 'ધ્યાનમાં રાખતા સભ્યોની સંખ્યા બતાવો',
 'tog-oldsig' => 'હાલના હસ્તાક્ષર',
@@ -419,6 +419,8 @@ $1',
 'youhavenewmessages' => 'તમારા માટે $1 ($2).',
 'newmessageslink' => 'નવીન સંદેશ',
 'newmessagesdifflink' => 'છેલ્લો ફેરફાર',
+'newmessageslinkplural' => '{{PLURAL:$1|નવો સંદેશ|નવાં સંદેશાઓ}}',
+'newmessagesdifflinkplural' => 'છેલ્લા {{PLURAL:$1|ફેરફાર|ફેરફારો}}',
 'youhavenewmessagesmulti' => '$1 ઉપર તમારા માટે નવો સંદેશ છે.',
 'editsection' => 'ફેરફાર કરો',
 'editsection-brackets' => '[$1]',
@@ -448,7 +450,7 @@ $1',
 
 # Short words for each namespace, by default used in the namespace tab in monobook
 'nstab-main' => 'લેખ',
-'nstab-user' => 'મારા àªµàª¿àª·à«\87',
+'nstab-user' => 'સભà«\8dય àªªàª¾àª¨à«\81àª\82',
 'nstab-media' => 'મિડીયા પાનું',
 'nstab-special' => 'ખાસ પાનું',
 'nstab-project' => 'પરિયોજનાનું પાનું',
@@ -543,6 +545,7 @@ $2',
 'ns-specialprotected' => 'ખાસ પાનાંમાં ફેરફાર ન થઇ શકે.',
 'titleprotected' => 'આ મથાળું (વિષય) [[User:$1|$1]] બનાવવા માટે સુરક્ષિત કરવામાં આવ્યો છે.
 આ માટેનું કારણ છે-- "\'\'$2\'\'".',
+'exception-nologin' => 'પ્રવેશ કરેલ નથી',
 
 # Virus scanner
 'virus-badscanner' => "ખરાબ રૂપરેખા: અજાણ્યું વાઇરસ સ્કેનર: ''$1''",
@@ -563,6 +566,7 @@ $2',
 'remembermypassword' => 'આ કોમ્યૂટર પર મારી લૉગ ઇન વિગતો ધ્યાનમાં રાખો (વધુમાં વધુ $1 {{PLURAL:$1|દિવસ|દિવસ}} માટે)',
 'securelogin-stick-https' => 'લોગ-ઈન કર્યા પછી  HTTPS સાથે જોડાયેલા રહો.',
 'yourdomainname' => 'તમારૂં ડોમેઇન:',
+'password-change-forbidden' => 'તમે આ વિકિ માટે પાસવર્ડ્સ બદલી શકતા નથી.',
 'externaldberror' => 'પ્રમાણભૂતતાની ત્રુટી આવી અથવા તમારૂ બહારનુ ખાતું અપડેટ કરવાનો અધિકાર તમને નથી.',
 'login' => 'પ્રવેશ કરો',
 'nav-login-createaccount' => 'પ્રવેશ કરો / નવું ખાતું ખોલો',
@@ -642,6 +646,7 @@ Please log in again after you receive it.
 'invalidemailaddress' => 'આ ઈ-મેલ અયોગ્ય માળખું ધરાવ્હે છે  માટે સ્વીકારી શકાશે નહીં
 કૃપયા યોગ્ય માળખામાં ઇ-મેલ લખો',
 'cannotchangeemail' => 'એકાઉન્ટ ઈ મેલ એડ્રસ આ વીકી પર નહિ બદલી શકાય.',
+'emaildisabled' => 'આ સાઇટ ઇ-મેલ્સ મોકલી શકતી નથી.',
 'accountcreated' => 'ખાતું ખોલવામાં આવ્યું છે',
 'accountcreatedtext' => '$1 માટે સભ્ય ખાતુ બનાવ્યું.',
 'createaccount-title' => '{{SITENAME}} માટે ખાતુ બનાવ્યું',
@@ -842,6 +847,7 @@ $2
 'note' => "'''નોંધ:'''",
 'previewnote' => "'''આ ફક્ત પૂર્વાવલોકન છે;'''
 તમારા ફેરફારો હજુ સાચવવામાં નથી આવ્યા!",
+'continue-editing' => 'વાંચવાનું ચાલુ રાખો',
 'previewconflict' => 'જો તમે આ પાનું સાચવશો તો આ પ્રિવ્યુમાં દેખાય છે તેવું સચવાશે.',
 'session_fail_preview' => "'''અફસોસ છે! સત્ર માહિતી ખોઇ દેવાને કારણે અમે તમારું કાર્ય સાચવી ન શક્યાં.'''
 કૃપયા ફરી પ્રયત્ન કરો.
@@ -857,6 +863,7 @@ $2
 જ્યારે તમે વેબ આધારિત  અજ્ઞાત પ્રોક્સી વાપરતા હોવ ત્યારે આવું બની શકે છે.",
 'edit_form_incomplete' => "'''ફેરફાર પત્રનો અમુક ભાગ સર્વર સુધી ન પહોંચ્યો; ખાત્રી કરો કે તમે કરેલા ફેરફાર બરાબર છે અને ફરી પ્રયત્ન કરો.'''",
 'editing' => '$1નો ફેરફાર કરી રહ્યા છે',
+'creating' => '$1 બનાવે છે',
 'editingsection' => '$1 (પરિચ્છેદ)નો ફેરફાર કરી રહ્યા છો',
 'editingcomment' => '$1 (પરિચ્છેદ)નો ફેરફાર કરી રહ્યા છો',
 'editconflict' => 'ફેરફારમાં વિસંગતતા: $1',
@@ -923,6 +930,7 @@ $2
 'edit-no-change' => 'તમારા ફેરફારો અવગણાયા, કેમકે અક્ષરકાયામાં કોઈ ફેરફારાના હતો',
 'edit-already-exists' => 'નવું પાનું બનાવી ન શકાયું
 તે પહેલેથી હાજર છે.',
+'defaultmessagetext' => 'મૂળભૂત સંદેશ લખાણ',
 
 # Parser/template warnings
 'expensive-parserfunction-warning' => "'''ચેતવણી:''' આ પાનું ખૂબ ખર્ચાળ પદચ્છેદ સૂત્ર ધરાવે છે.
@@ -1119,6 +1127,8 @@ $1",
 
 # Diffs
 'history-title' => '"$1"ના ફેરફારોનો ઇતિહાસ',
+'difference-title' => '"$1" ની આવૃત્તિઓ વચ્ચેનો તફાવત',
+'difference-title-multipage' => '"$1" અને "$2" પાનાંઓ વચ્ચેનો તફાવત',
 'difference-multipage' => '(પાનાઓ વચ્ચેનો ફેરફાર)',
 'lineno' => 'લીટી $1:',
 'compareselectedversions' => 'પસંદ કરેલા સરખાવો',
@@ -1216,6 +1226,7 @@ $1",
 'prefs-beta' => 'બીટા લક્ષણો',
 'prefs-datetime' => 'તારીખ અને સમય',
 'prefs-labs' => 'પ્રયોગશાળા લક્ષણો',
+'prefs-user-pages' => 'સભ્ય પાનાંઓ',
 'prefs-personal' => 'સભ્ય ઓળખ',
 'prefs-rc' => 'તાજા ફેરફારો',
 'prefs-watchlist' => 'ધ્યાનસૂચિ',
@@ -1240,7 +1251,7 @@ $1",
 'searchresultshead' => 'શોધો',
 'resultsperpage' => 'પ્રતિ પાના પર પરિણામો',
 'stub-threshold' => '<a href="#" class="stub">stub link</a>નાફોર્મમેટિંગ માટે શરૂઆતિ પગથિયું  (બાઈટ્સ):',
-'stub-threshold-disabled' => 'નિષà«\8dàª\95à«\8dરà«\80યાનà«\8dવà«\80ત',
+'stub-threshold-disabled' => 'નિષà«\8dàª\95à«\8dરિય àª\95રà«\87લ',
 'recentchangesdays' => 'તાજા ફેરફારોમાં દેખાડવાના દિવસો',
 'recentchangesdays-max' => 'મહત્તમ  $1 {{PLURAL:$1|દિવસ|દિવસો}}',
 'recentchangescount' => 'સમાન્ય પણે ફલકમાં બતાવવાના ફેરફારોની સંખ્યા',
@@ -1441,7 +1452,7 @@ HTML નાકું ચકાસો',
 
 # Associated actions - in the sentence "You do not have permission to X"
 'action-read' => 'આ પાનું વાંચો.',
-'action-edit' => 'àª\86 àªªàª¾àª¨àª¾àª®àª¾àª\82 àª«à«\87રફાર àª\95રવાનà«\80',
+'action-edit' => 'àª\86 àªªàª¾àª¨àª¾àª®àª¾àª\82 àª«à«\87રફાર àª\95રà«\8b',
 'action-createpage' => 'નવો લેખ શરૂ કરો',
 'action-createtalk' => 'ચર્ચાનું પાનું બનાવો',
 'action-createaccount' => ' ખાતું ખોલો',
@@ -1496,7 +1507,7 @@ HTML નાકું ચકાસો',
 'rcshowhidepatr' => ' $1 ચોકીયાત ફેરફારો',
 'rcshowhidemine' => 'મારા ફેરફારો $1',
 'rclinks' => 'છેલ્લાં $2 દિવસમાં થયેલા છેલ્લાં $1 ફેરફારો દર્શાવો<br />$3',
-'diff' => 'ભà«\87દ',
+'diff' => 'તફાવત',
 'hist' => 'ઇતિહાસ',
 'hide' => 'છુપાવો',
 'show' => 'બતાવો',
@@ -1893,9 +1904,9 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization. જુઓ',
 
 'disambiguations' => 'સંદિગ્ધ શીર્ષકવાળાં પાનાં સાથે જોડાતાં પૃષ્ઠો',
 'disambiguationspage' => 'Template:અસંદિગ્ધ',
-'disambiguations-text' => "નીચેના પાના  '''સંદિગ્ધ વાક્યો વાળા પાના''' સાથે કડી દ્વારા જોડાયેલા છે.
-તà«\87ના àª\95રતા àª¤à«\87નà«\87 àª¯à«\8bàª\97à«\8dય àª¤à«\87 àªµàª¿àª·à«\8dય સાથે જોડાયેલા હોવા જોઇએ.<br />
-àª\86 àªªàª¾àª¨àª¾àª¨à«\87 àª¸àª\82દિàª\97à«\8dધ  àªµàª¾àª\95à«\8dયà«\8b àªµàª¾àª³àª¾ àªªàª¾àª¨àª¾ àª¤à«\8dયારà«\87 àª\95હà«\80 àª¶àª\95ાય àª\9cà«\8dયારà«\87 àª¤à«\87 [[MediaWiki:Disambiguationspage]] àª¨àª¾àª®àª¨àª¾ àª¢àª¾àª\82àª\9aા àª¸àª¾àª¥à«\87 àª\9cà«\8bડાયà«\87લા હોય.",
+'disambiguations-text' => "નà«\80àª\9aà«\87ના àªªàª¾àª¨àª¾  '''સàª\82દિàª\97à«\8dધ àªµàª¾àª\95à«\8dયà«\8b àªµàª¾àª³àª¾ àªªàª¾àª¨àª¾''' àª¸àª¾àª¥à«\87 àª\93àª\9bામાàª\82 àª\93àª\9bà«\80 àª\8fàª\95 àª\95ડà«\80 àª¦à«\8dવારા àª\9cà«\8bડાયà«\87લા àª\9bà«\87.
+તà«\87àª\93 àªµàª§à«\81 àª¯à«\8bàª\97à«\8dય àªªàª¾àª¨àª¾àª\82 સાથે જોડાયેલા હોવા જોઇએ.<br />
+પાનાનà«\87 àª¸àª\82દિàª\97à«\8dધ àªµàª¾àª\95à«\8dયà«\8b àªµàª¾àª³à«\81àª\82 àªªàª¾àª¨à«\81àª\82 àª¤à«\8dયારà«\87 àª\95હà«\80 àª¶àª\95ાય àª\9cà«\8dયારà«\87 àª¤à«\87 [[MediaWiki:Disambiguationspage]] àª¨àª¾àª®àª¨àª¾ àª®àª¾àª³àª\96ા àª¸àª¾àª¥à«\87 àª\9cà«\8bડાયà«\87લà«\81àª\82 હોય.",
 
 'doubleredirects' => 'બમણું દિશાનિર્દેશન',
 'doubleredirectstext' => 'આ પાનું દિશા નિર્દેશિત પાના પર થયેલા દિશા નિર્દેશિત પાનાની યાદિ બતાવે છે.
@@ -2242,6 +2253,8 @@ Deleting it may disrupt database operations of {{SITENAME}};',
 'rollback' => 'ફેરફારો ઉલટાવો',
 'rollback_short' => 'ઉલટાવો',
 'rollbacklink' => 'પાછું વાળો',
+'rollbacklinkcount' => '$1 {{PLURAL:$1|ફેરફાર|ફેરફારો}} કરતાં વધુ પાછાં લાવો',
+'rollbacklinkcount-morethan' => '$1 {{PLURAL:$1|ફેરફાર|ફેરફારો}} કરતાં ઓછું પાછું લાવો',
 'rollbackfailed' => 'ઉલટાવવું નિષ્ફળ',
 'cantrollback' => 'આ ફેરફારો ઉલટાવી નહી શકાય
 છેલ્લો ફેરફાર આ પાના ના રચયિતા દ્વારા જ થયો હતો',
@@ -2407,7 +2420,7 @@ To perform a selective restoration, check the boxes corresponding to the revisio
 'sp-contributions-deleted' => 'સભ્યનું ભૂંસેલું યોગદાન',
 'sp-contributions-uploads' => 'ખાસ યોગદાન / ચડાવેલ ફાઇલ',
 'sp-contributions-logs' => 'લૉગ',
-'sp-contributions-talk' => 'યà«\8bàª\97દાનàª\95રà«\8dતાનà«\80 àª\9aરà«\8dàª\9aા',
+'sp-contributions-talk' => 'ચર્ચા',
 'sp-contributions-userrights' => 'સભ્ય હક્ક પ્રબંધન',
 'sp-contributions-blocked-notice' => 'આ સભ્ય પ્રતિબંધિત છે
 તમારા સંદર્ભ માટે પ્રતિબંધિત સભ્યોની યાદિ આપી છે',
@@ -3572,7 +3585,7 @@ $5
 'version-extensions' => 'પ્રસ્થાપિત વિસ્તારકો',
 'version-specialpages' => 'ખાસ પાનાં',
 'version-parserhooks' => 'પદચ્છેદ ખૂંટો',
-'version-variables' => 'સહàª\97à«\81ણàª\95à«\8b',
+'version-variables' => 'àª\9aલ',
 'version-antispam' => 'સ્પેમ સંરક્ષણ',
 'version-skins' => 'ફલક',
 'version-other' => 'અન્ય',
@@ -3596,6 +3609,7 @@ $5
 'version-software-product' => 'ઉત્પાદ',
 'version-software-version' => 'આવૃત્તિ',
 'version-entrypoints-header-entrypoint' => 'પ્રવેશ સ્થળ',
+'version-entrypoints-header-url' => 'URL',
 
 # Special:FilePath
 'filepath' => 'ફાઈલ પથ',
@@ -3785,7 +3799,7 @@ $5
 'api-error-verification-error' => 'ફાઈલ યાતો ખરાબ થઈ ગઈ છે યાતો તેનું ઍક્સટેન્શન (વિસ્તરક) ખોટો છે.',
 
 # Durations
-'duration-seconds' => '$1 {{PLURAL:$1|સેકંડ|સેકંડ}}',
+'duration-seconds' => '$1 {{PLURAL:$1|સેકંડ|સેકંડ}}',
 'duration-minutes' => '$1 {{PLURAL:$1|મિનિટ|મિનિટો}}',
 'duration-hours' => '$1 {{PLURAL:$1|કલાક|કલાકો}}',
 'duration-days' => '$1 {{PLURAL:$1|દિવસ|દિવસો}}',
@@ -3796,5 +3810,5 @@ $5
 'duration-millennia' => '$1 {{PLURAL:$1|સહસ્ત્રાબ્દી|સહસ્ત્રાબ્દીઓ}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|એ ફાઇલ પ્રકારને પ્રવાનગી નથી|એ ફાઇલ પ્રકારોને પ્રવાનગી નથી }}. પરવાનગી મેળવેલ {{PLURAL:$3|ફાઇલ પ્રકાર છે|ફાઇલ પ્રકારો  છે}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4| માન્ય ફાઇલ પ્રકાર નથી| માન્ય ફાઇલ પ્રકારો નથી }}. માન્ય {{PLURAL:$3|ફાઇલ પ્રકાર છે|ફાઇલ પ્રકારો  છે}} $2.',
 );
index d246350..1fecb95 100644 (file)
@@ -619,6 +619,10 @@ $1',
 'youhavenewmessages' => 'יש לך $1 ($2).',
 'newmessageslink' => 'הודעות חדשות',
 'newmessagesdifflink' => 'השוואה לגרסה הקודמת',
+'youhavenewmessagesfromusers' => 'יש לך $1 {{PLURAL:$3|ממשתמש אחר|מ־$3 משתמשים}} ($2).',
+'youhavenewmessagesmanyusers' => 'יש לך $1 ממשתמשים רבים ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|הודעה חדשה|הודעות חדשות}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|שינוי אחרון|שינויים אחרונים}}',
 'youhavenewmessagesmulti' => 'יש לך הודעות חדשות ב־$1',
 'editsection' => 'עריכה',
 'editold' => 'עריכה',
@@ -1151,6 +1155,7 @@ $2
 'expansion-depth-exceeded-warning' => 'עומק ההרחבה בדף גדול מדי',
 'parser-unstrip-loop-warning' => 'נמצאה לולאה בפריסה',
 'parser-unstrip-recursion-limit' => 'עומק הרקורסיה של הפריסה עבר את המגבלה ($1)',
+'converter-manual-rule-error' => 'התגלתה שגיאה בכלל המרת שפה ידני',
 
 # "Undo" feature
 'undo-success' => 'ניתן לבטל את העריכה. אנא בִדקו את השוואת הגרסאות למטה כדי לוודא שזה מה שאתם רוצים לעשות, ואז שמרו את השינויים למטה כדי לבצע את ביטול העריכה.',
@@ -2115,7 +2120,7 @@ $1',
 'statistics-pages' => 'דפים',
 'statistics-pages-desc' => 'כל הדפים באתר הוויקי, כולל דפי שיחה, הפניות, וכדומה',
 'statistics-files' => 'קבצים שהועלו',
-'statistics-edits' => '×\94ער×\99×\9b×\95ת ×\9e×\90×\96 ×ª×\97×\99×\9cת ×\94פע×\95×\9c×\94 ×©×\9c {{SITENAME}}',
+'statistics-edits' => 'ער×\99×\9b×\95ת ×©×\9c ×\93פ×\99×\9d ×\9e×\90×\96 ×\94תקנת {{SITENAME}}',
 'statistics-edits-average' => 'מספר העריכות הממוצע לדף',
 'statistics-views-total' => 'מספר הצפיות הכולל',
 'statistics-views-total-desc' => 'צפיות בדפים שאינם קיימים ובדפים מיוחדים אינן כלולות',
index 0cf8e36..b1be847 100644 (file)
@@ -417,6 +417,10 @@ $1',
 'youhavenewmessages' => 'Maš $1 ($2).',
 'newmessageslink' => 'nowe powěsće',
 'newmessagesdifflink' => 'poslednja změna',
+'youhavenewmessagesfromusers' => 'Maš $1 wot {{PLURAL:$3|druheho wužiwarja|$3 wužiwarjow|$3 wužiwarjow|$3 wužiwarjow}} ($2).',
+'youhavenewmessagesmanyusers' => 'Maš $1 wot wjele wužiwarjow ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|nowa powěsć|nowej powěsći|nowe powěsće|nowe powěsće}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|poslednja změna|poslednjej změnje|poslednje změny|podslednje změny}}',
 'youhavenewmessagesmulti' => 'Maš nowe powěsće: $1',
 'editsection' => 'wobdźěłać',
 'editold' => 'wobdźěłać',
@@ -913,6 +917,7 @@ Dyrbjała mjenje hač $2 {{PLURAL:$2|wołanje|wołanjej|wołanja|wołanjow}} mě
 'expansion-depth-exceeded-warning' => 'Strona je ekspansisku hłubokosć překročił',
 'parser-unstrip-loop-warning' => 'Njeskónčna sekla namakana',
 'parser-unstrip-recursion-limit' => 'Rekursiska hranica překročena ($1)',
+'converter-manual-rule-error' => 'Zmylk w manuelnym prawidle rěčneho konwertowanja namakany',
 
 # "Undo" feature
 'undo-success' => 'Wersija je so wuspěšnje wotstroniła. Prošu přepruwuj deleka w přirunanskim napohledźe, hač twoja změna bu přewzata a klikń potom na „Składować”, zo by změnu składował.',
index 3e18eb9..82732fd 100644 (file)
@@ -577,6 +577,9 @@ További információkat a [[Special:Version|verzióinformációs lapon]] talál
 'youhavenewmessages' => 'Új üzenet vár $1! (Az üzenetet $2.)',
 'newmessageslink' => 'a vitalapodon',
 'newmessagesdifflink' => 'külön is megtekintheted',
+'youhavenewmessagesmanyusers' => '$1ed van több szerkesztőtől ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|egy|$1}} új üzenet',
+'newmessagesdifflinkplural' => 'utolsó {{PLURAL:$1|egy|$1}} változtatás',
 'youhavenewmessagesmulti' => 'Új üzenetet vár a(z) $1 wikin',
 'editsection' => 'szerkesztés',
 'editold' => 'szerkesztés',
@@ -695,6 +698,13 @@ $2',
 'ns-specialprotected' => 'A speciális lapok nem szerkeszthetőek.',
 'titleprotected' => "Ilyen címmel nem lehet szócikket készíteni, [[User:$1|$1]] letiltotta.
 A blokkolás oka: „''$2''”.",
+'filereadonlyerror' => 'A(z) "$1" fájl nem módosítható, mert a(z) "$2" fájltároló csak olvasható módban üzemel.
+
+A lezárást végrehajtó rendszergazda az alábbi indoklást adta meg: "$3".',
+'invalidtitle-knownnamespace' => 'Érvénytelen cím "$2" névtérrel és "$3" szöveggel',
+'invalidtitle-unknownnamespace' => 'Érvénytelen cím az ismeretlen $1 névtérszámmal és "$2" szöveggel',
+'exception-nologin' => 'Nem vagy bejelentkezve.',
+'exception-nologin-text' => 'Ezen lap vagy művelet használatához be kell jelenetkezned erre a wikire.',
 
 # Virus scanner
 'virus-badscanner' => "Hibás beállítás: ismeretlen víruskereső: ''$1''",
@@ -715,6 +725,7 @@ Ne felejtsd el átnézni a [[Special:Preferences|személyes beállításaidat]].
 'remembermypassword' => 'Emlékezzen rám ezen a számítógépen (legfeljebb $1 napig)',
 'securelogin-stick-https' => 'Kapcsolódás HTTPS-en keresztül bejelentkezés után is',
 'yourdomainname' => 'A domainneved:',
+'password-change-forbidden' => 'Nem módosíthatod a jelszót ezen a wikin.',
 'externaldberror' => 'Hiba történt a külső adatbázis hitelesítése közben, vagy nem vagy jogosult a külső fiókod frissítésére.',
 'login' => 'Bejelentkezés',
 'nav-login-createaccount' => 'Bejelentkezés / fiók létrehozása',
@@ -956,6 +967,9 @@ Ha úgy látod, hogy az üzenetek, amiket ide kapsz, nem neked szólnak, [[Speci
 vagy [{{fullurl:{{FULLPAGENAME}}|action=edit}} szerkesztheted a lapot].</span>',
 'noarticletext-nopermission' => 'Ez a lap jelenleg nem tartalmaz szöveget.
 [[Special:Search/{{PAGENAME}}|Rákereshetsz a lap címére]] más lapok tartalmában, vagy <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} megtekintheted a kapcsolódó naplófájlokat]</span>.',
+'missing-revision' => 'A(z) "{{PAGENAME}}" nevű oldal #$1 változata nem létezik.
+
+Ezt általában egy elavult, törölt oldalra mutató laptörténeti hivatkozás használata okozza. Részletek a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} törlési naplóban] találhatóak.',
 'userpage-userdoesnotexist' => 'Nincs „<nowiki>$1</nowiki>” nevű regisztrált felhasználónk.
 Nézd meg, hogy valóban ezt a lapot szeretnéd-e létrehozni vagy szerkeszteni.',
 'userpage-userdoesnotexist-view' => 'Nincs regisztrálva „$1” szerkesztői azonosító.',
@@ -3907,6 +3921,8 @@ A képek teljes méretben jelennek meg, más fájltípusok közvetlenül a hozz
 'api-error-empty-file' => 'Az általad elküldött fájl üres volt.',
 'api-error-emptypage' => 'Új, üres lap létrehozása nem engedélyezett.',
 'api-error-fetchfileerror' => 'Belső hiba: valami baj történt a fájl beolvasása közben.',
+'api-error-fileexists-forbidden' => 'Már létezik „$1” nevű fájl, és nem lehet felülírni.',
+'api-error-fileexists-shared-forbidden' => 'Már létezik „$1” nevű fájl a megosztott fájlok között, és nem lehet felülírni.',
 'api-error-file-too-large' => 'Az általad elküldött fájl túl nagy.',
 'api-error-filename-tooshort' => 'A fájlnév túl rövid.',
 'api-error-filetype-banned' => 'Tiltott fájltípus.',
@@ -3946,5 +3962,5 @@ A képek teljes méretben jelennek meg, más fájltípusok közvetlenül a hozz
 'duration-millennia' => '{{PLURAL:$1|egy|$1}} évezred',
 
 # Unknown messages
-'api-error-filetype-banned-type' => 'A következő {{PLURAL:$4|fájltípus nem engedélyezett|fájltípusok nem engedélyezettek}}: $1 Engedélyezett {{PLURAL:$3|típus|típusok}}: $2.',
+'api-error-filetype-banned-type' => '!A következő {{PLURAL:$4|fájltípus nem engedélyezett|fájltípusok nem engedélyezettek}}: $1. Engedélyezett {{PLURAL:$3|típus|típusok}}: $2.',
 );
index c984405..3544257 100644 (file)
@@ -2507,6 +2507,9 @@ $3
 'watchlisttools-edit' => 'Դիտել և խմբագրել հսկացանկը',
 'watchlisttools-raw' => 'Խմբագրել հում հսկացանկը',
 
+# Signatures
+'signature' => '[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|քննարկում]])',
+
 # Special:Version
 'version' => 'ՄեդիաՎիքի տարբերակը',
 
index d590645..0b5f358 100644 (file)
@@ -407,6 +407,10 @@ $1',
 'youhavenewmessages' => 'Tu ha $1 ($2).',
 'newmessageslink' => 'nove messages',
 'newmessagesdifflink' => 'ultime modification',
+'youhavenewmessagesfromusers' => 'Tu ha $1 de {{PLURAL:$3|un altere usator|$3 usatores}} ($2).',
+'youhavenewmessagesmanyusers' => 'Tu ha $1 de multe usatores ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|un nove message|$1 nove messages}}',
+'newmessagesdifflinkplural' => 'ultime {{PLURAL:$1|modification|modificationes}}',
 'youhavenewmessagesmulti' => 'Tu ha nove messages in $1',
 'editsection' => 'modificar',
 'editold' => 'modificar',
index 6cebfbd..f93f6c6 100644 (file)
@@ -721,6 +721,8 @@ Alasan yang diberikan adalah ''$2''.",
 Administrator yang terkunci menawarkan penjelasan ini: " $3 ".',
 'invalidtitle-knownnamespace' => 'Judul yang tidak sah dengan ruangnama "$2" dan teks "$3"',
 'invalidtitle-unknownnamespace' => 'Judul yang tidak sah dengan nomor ruang nama tidak diketahui $1 dan teks "$2"',
+'exception-nologin' => 'Belum masuk log',
+'exception-nologin-text' => 'Halaman atau tindakan ini mengharuskan Anda masuk log di wiki ini.',
 
 # Virus scanner
 'virus-badscanner' => "Kesalahan konfigurasi: pemindai virus tidak dikenal: ''$1''",
@@ -741,6 +743,7 @@ Akun Anda telah dibuat. Jangan lupa mengatur konfigurasi [[Special:Preferences|p
 'remembermypassword' => 'Ingat kata sandi saya di komputer ini (selama $1 {{PLURAL:$1|hari|hari}})',
 'securelogin-stick-https' => 'Tetap terhubung ke HTTPS setelah masuk',
 'yourdomainname' => 'Domain Anda:',
+'password-change-forbidden' => 'Anda tidak dapat mengubah kata sandi pada wiki ini.',
 'externaldberror' => 'Telah terjadi kesalahan otentikasi basis data eksternal atau Anda tidak diizinkan melakukan kemaskini terhadap akun eksternal Anda.',
 'login' => 'Masuk log',
 'nav-login-createaccount' => 'Masuk log / buat akun',
@@ -981,6 +984,10 @@ Jika Anda adalah seorang pengguna anonim dan merasa mendapatkan komentar-komenta
 Anda dapat [[Special:Search/{{PAGENAME}}|melakukan pencarian untuk judul halaman ini]] di halaman-halaman lain, <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mencari log terkait], atau [{{fullurl:{{FULLPAGENAME}}|action=edit}} menyunting halaman ini]</span>.',
 'noarticletext-nopermission' => 'Saat ini tidak ada teks di halaman ini.
 Anda dapat [[Special:Search/{{PAGENAME}}|melakukan pencarian untuk judul halaman ini]] di halaman-halaman lain, <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mencari log terkait], atau [{{fullurl:{{FULLPAGENAME}}|action=edit}} menyunting halaman ini]</span>.',
+'missing-revision' => 'Revisi #$1 halaman berjudul "{{PAGENAME}}" tidak eksks.
+
+Hal ini biasanya disebabkan oleh tautan versi terdahulu menuju halaman yang sudah dihapus.
+Rinciannya dapat ditemukan di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log penghapusan].',
 'userpage-userdoesnotexist' => 'Akun pengguna "<nowiki>$1</nowiki>" tidak terdaftar.',
 'userpage-userdoesnotexist-view' => 'Pengguna "$1" tidak terdaftar.',
 'blocked-notice-logextract' => 'Pengguna ini sedang diblokir.
@@ -2388,6 +2395,8 @@ Menghapus halaman ini dapat menyebabkan masalah dalam operasional basis data {{S
 'rollback' => 'Kembalikan suntingan',
 'rollback_short' => 'Kembalikan',
 'rollbacklink' => 'kembalikan',
+'rollbacklinkcount' => 'kembalikan $1 {{PLURAL:$1|suntingan|suntingan}}',
+'rollbacklinkcount-morethan' => 'kembalikan lebih dari $1 {{PLURAL:$1|suntingan|suntingan}}',
 'rollbackfailed' => 'Pengembalian gagal dilakukan',
 'cantrollback' => 'Tidak dapat mengembalikan suntingan;
 kontributor terakhir adalah satu-satunya penulis halaman ini.',
index 8eef462..2c774a7 100644 (file)
@@ -2437,7 +2437,7 @@ Síðasta færsla vistfangsins úr bönnunarskrá er sýnd hér fyrir neðan til
 'whatlinkshere-links' => '← tenglar',
 'whatlinkshere-hideredirs' => '$1 tilvísanir',
 'whatlinkshere-hidetrans' => '$1 ítengingar',
-'whatlinkshere-hidelinks' => '$1 tenglar',
+'whatlinkshere-hidelinks' => '$1 tengla',
 'whatlinkshere-hideimages' => '$1 myndatenglar',
 'whatlinkshere-filters' => 'Síur',
 
index 4a7e1a2..b2aadd7 100644 (file)
@@ -285,7 +285,7 @@ $messages = array(
 'tog-minordefault' => 'Indica ogni modifica come minore (solo come predefinito)',
 'tog-previewontop' => "Mostra l'anteprima sopra la casella di modifica e non sotto",
 'tog-previewonfirst' => "Mostra l'anteprima per la prima modifica",
-'tog-nocache' => 'Disabilitare la cache delle pagine del browser',
+'tog-nocache' => 'Disabilita la cache delle pagine del browser',
 'tog-enotifwatchlistpages' => 'Inviami una email quando viene modificata una pagina o un file presente tra gli osservati speciali',
 'tog-enotifusertalkpages' => 'Segnalami via e-mail le modifiche alla mia pagina di discussione',
 'tog-enotifminoredits' => 'Inviami una email anche per le modifiche minori di pagine e file',
@@ -527,7 +527,11 @@ $1',
 'retrievedfrom' => 'Estratto da "$1"',
 'youhavenewmessages' => 'Hai $1 ($2).',
 'newmessageslink' => 'nuovi messaggi',
-'newmessagesdifflink' => 'differenza con la revisione precedente',
+'newmessagesdifflink' => 'ultima modifica',
+'youhavenewmessagesfromusers' => 'Hai $1 da {{PLURAL:$3|un altro utente|$3 utenti}} ($2).',
+'youhavenewmessagesmanyusers' => 'Hai $1 da molti utenti ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|un nuovo messaggio|nuovi messaggi}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|ultima modifica|ultime modifiche}}',
 'youhavenewmessagesmulti' => 'Hai nuovi messaggi su $1',
 'editsection' => 'modifica',
 'editold' => 'modifica',
@@ -1033,6 +1037,7 @@ Alcuni template non verranno inclusi.",
 'expansion-depth-exceeded-warning' => 'Questa pagina ha superato la profondità di espansione',
 'parser-unstrip-loop-warning' => 'Rilevato ciclo di Unstrip',
 'parser-unstrip-recursion-limit' => 'Superati i limiti di ricorsione di Unstrip ($1)',
+'converter-manual-rule-error' => 'Rilevato errore nella regola manuale di conversione della lingua',
 
 # "Undo" feature
 'undo-success' => 'Questa modifica può essere annullata. Verificare il confronto presentato di seguito per accertarsi che il contenuto corrisponda a quanto desiderato e quindi salvare le modifiche per completare la procedura di annullamento.',
index b12be56..b93471c 100644 (file)
@@ -722,6 +722,8 @@ URL を間違って入力したか、正しくないリンクをたどった可
 'cannotdelete' => 'ページまたはファイル「$1」を削除できませんでした。
 他の人が既に削除した可能性があります。',
 'cannotdelete-title' => '「$1」というページを削除できません',
+'delete-hook-aborted' => 'フックによって削除が中止されました。
+理由は不明です。',
 'badtitle' => '正しくないページ名',
 'badtitletext' => '要求されたページ名は、無効、空、正しくない言語間リンク/ウィキ間リンクのページ名、のいずれかです。
 ページ名に使用できない文字が1つ以上含まれている可能性があります。',
@@ -757,6 +759,7 @@ $2',
 
 読み取り専用に設定した管理者からの説明:「$3」',
 'exception-nologin' => 'ログインしていません',
+'exception-nologin-text' => 'このページまたは操作には、このウィキへのログインが必要です。',
 
 # Virus scanner
 'virus-badscanner' => "環境設定が不適合です:不明なウイルス検知ソフトウェア:''$1''",
@@ -777,6 +780,7 @@ $2',
 'remembermypassword' => 'このブラウザーにログイン情報を保存 (最長 $1 {{PLURAL:$1|日|日間}})',
 'securelogin-stick-https' => 'ログイン後にHTTPS接続を維持',
 'yourdomainname' => 'ドメイン:',
+'password-change-forbidden' => 'このウィキではパスワードを変更できません。',
 'externaldberror' => '外部の認証データベースでエラーが発生したか、または外部アカウント情報の更新が許可されていません。',
 'login' => 'ログイン',
 'nav-login-createaccount' => 'ログインまたはアカウント作成',
@@ -1174,6 +1178,7 @@ IP アドレスは複数の利用者で共有されている場合がありま
 'expansion-depth-exceeded-warning' => 'ページが展開の深さ制限を超えました',
 'parser-unstrip-loop-warning' => 'Unstrip のループが検出されました',
 'parser-unstrip-recursion-limit' => 'Unstrip の再帰($1)が上限を超えました',
+'converter-manual-rule-error' => '手動の言語変換規則でエラーを検出しました。',
 
 # "Undo" feature
 'undo-success' => 'この編集を取り消せます。
@@ -1635,6 +1640,7 @@ $1 {{PLURAL:$1|文字}}以下である必要があります。',
 'right-writeapi' => '書き込みAPIの使用',
 'right-delete' => 'ページの削除',
 'right-bigdelete' => '大きな履歴があるページを削除',
+'right-deletelogentry' => '特定の記録項目の削除と復帰',
 'right-deleterevision' => 'ページの特定の版の削除と復帰',
 'right-deletedhistory' => '削除された履歴項目(関連する本文を除く)を閲覧',
 'right-deletedtext' => '削除された本文と削除された版間の差分を閲覧',
@@ -2232,7 +2238,7 @@ contenttype/subtypeの形式で入力してください(例:<tt>image/jpeg</
 'protectedtitles' => '作成保護されているページ名',
 'protectedtitlestext' => '以下のページは新規作成が禁止されています',
 'protectedtitlesempty' => 'これらの引数で現在保護されているページはありません。',
-'listusers' => '利用者一覧',
+'listusers' => '利用者一覧',
 'listusers-editsonly' => '投稿記録のある利用者のみを表示',
 'listusers-creationsort' => '作成日順に並べ替え',
 'usereditcount' => '$1{{PLURAL:$1|回の編集}}',
@@ -2388,7 +2394,7 @@ contenttype/subtypeの形式で入力してください(例:<tt>image/jpeg</
 'emailccsubject' => '$1に送信したメールの控え:$2',
 'emailsent' => 'メールを送信しました',
 'emailsenttext' => 'メールを送信しました。',
-'emailuserfooter' => 'このメールは {{SITENAME}} の「利用者にメール送信」機能が、「$1」から「$2」に送信したものです。',
+'emailuserfooter' => 'このメールは$1から$2へ、{{SITENAME}}の「利用者にメールを送信」機能でお送りしました。',
 
 # User Messenger
 'usermessage-summary' => 'システムメッセージを残す。',
@@ -4212,6 +4218,8 @@ MediaWikiは、有用であることを期待して配布されていますが
 'api-error-empty-file' => '送信されたファイルは空でした。',
 'api-error-emptypage' => '内容がないページの新規作成は許可されていません。',
 'api-error-fetchfileerror' => '内部エラー:ファイルの取得中に問題が発生しました。',
+'api-error-fileexists-forbidden' => '「$1」という名前のファイルは存在しており、上書きはできません。',
+'api-error-fileexists-shared-forbidden' => '「$1」という名前のファイルは共有ファイルリポジトリに存在しており、上書きはできません。',
 'api-error-file-too-large' => '送信されたファイルは大きすぎます。',
 'api-error-filename-tooshort' => 'ファイル名が短すぎます。',
 'api-error-filetype-banned' => 'この形式のファイルは禁止されています。',
@@ -4251,5 +4259,5 @@ MediaWikiは、有用であることを期待して配布されていますが
 'duration-millennia' => '$1{{PLURAL:$1|,000 年}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 は許可されていないファイル形式です{{PLURAL:$4|}}。 許可されているファイル形式{{PLURAL:$3|}}は$2です。',
+'api-error-filetype-banned-type' => '$1{{PLURAL:$4|は許可されていないファイル形式です}}。許可されている{{PLURAL:$3|ファイル形式}}は$2です。',
 );
index 2aa66fb..da4e40b 100644 (file)
@@ -11,6 +11,7 @@
  * @author Helix84
  * @author Kaganer
  * @author Meursault2004
+ * @author NoiX180
  * @author Pras
  * @author Rex
  * @author StefanusRA
@@ -54,7 +55,7 @@ $messages = array(
 'tog-hidepatrolled' => 'Delikna suntingan sing wis dipatroli ing kaca owah-owahan',
 'tog-newpageshidepatrolled' => 'Delikna kaca sing wis dipatroli saka daftar kaca anyar',
 'tog-extendwatchlist' => 'Kembangna daftar pangawasan kanggo nuduhaké kabèh pangowahan, ora mung sing paling anyar',
-'tog-usenewrc' => 'Gunakna tampilan pangowahan pungkasan sing wis dikembangake (mbutuhake JavaScript)',
+'tog-usenewrc' => 'Owah-owahané paguyuban miturut kaca nèng owah-owahan anyar lan daptar panto (mbutuhaké JavaScript)',
 'tog-numberheadings' => 'Wènèhana nomer judul secara otomatis',
 'tog-showtoolbar' => 'Tuduhna <em>toolbar</em> (batang piranti) panyuntingan',
 'tog-editondblclick' => 'Sunting kaca nganggo klik ping loro (JavaScript)',
@@ -62,17 +63,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Fungsèkna panyuntingan sub-bagian mawa klik-tengen ing judul bagian (JavaScript)',
 'tog-showtoc' => 'Tuduhna daftar isi (kanggo kaca sing nduwé luwih saka 3 sub-bagian)',
 'tog-rememberpassword' => 'Émut tembung sandi kula ing peramban punika (salebeting $1 {{PLURAL:$1|dinten|dinten}})',
-'tog-watchcreations' => 'Tambahna kaca sing tak-gawé ing daftar pangawasan',
-'tog-watchdefault' => 'Tambahna kaca sing tak-sunting ing daftar pangawasan',
-'tog-watchmoves' => 'Tambahkan kaca sing tak-pindhah ing daftar pangawasan',
-'tog-watchdeletion' => 'Tambahkan kaca sing tak-busak ing daftar pangawasan',
+'tog-watchcreations' => 'Tambahaké kaca sing tak gawé lan berkas sing tak unggah nèng daptar pangawasan',
+'tog-watchdefault' => 'Tambahaké kaca lan berkas sing tak sunting nèng daptar pangawasan',
+'tog-watchmoves' => 'Tambahaké kaca lan berkas sing tak pindhahaké nèng daptar pangawasan',
+'tog-watchdeletion' => 'Tambahaké kaca lan berkas sing tak busak nèng daptar pangawasan',
 'tog-minordefault' => 'Tandhanana kabèh suntingan dadi suntingan cilik secara baku',
 'tog-previewontop' => 'Tuduhna pratayang sadurungé kothak sunting lan ora sawisé',
 'tog-previewonfirst' => 'Tuduhna pratayang ing suntingan kapisan',
 'tog-nocache' => 'Nonaktifaken penyinggahan kaca peramban',
-'tog-enotifwatchlistpages' => 'Kirimana aku layang e-mail yèn ana sawijining kaca sing tak-awasi owah',
+'tog-enotifwatchlistpages' => 'Kirimi kula layang èlèktronik yèn ana kaca utawa berkas nèng daptar pangawasanku sing diowah',
 'tog-enotifusertalkpages' => 'Kirimana aku layang e-mail yèn kaca dhiskusiku owah',
-'tog-enotifminoredits' => 'Kirimana aku layang e-mail uga yèn ana pangowahan cilik',
+'tog-enotifminoredits' => 'Kirimi kula layang èlèktronik uga yèn ana suntingan cilik saka kaca lan berkas',
 'tog-enotifrevealaddr' => 'Kirimana aku layang e-mail ing layang notifikasi',
 'tog-shownumberswatching' => 'Tuduhna cacahé pangawas',
 'tog-oldsig' => 'Tapak asma sing ana:',
@@ -808,12 +809,14 @@ Pangurus {{SITENAME}} liyané bakal tetep bisa ngaksès isi sing kadhelikaké ik
 'revdelete-log' => 'Alesan:',
 'revdelete-submit' => 'Trapna ing {{PLURAL:$1|révisi|révisi}} kapilih',
 'revdelete-success' => 'Aturan pandhelikan revisi bisa kasil ditrapaké.',
-'revdelete-failure' => "'''Mode tampilan révisi ora bisa disèt:'''
+'revdelete-failure' => "'''Panampakan rèvisi ora bisa dianyari:'''
 $1",
 'logdelete-success' => 'Aturan pandhelikan tindhakan bisa kasil ditrapaké.',
 'logdelete-failure' => "'''Aturan pandhelikan ora bisa disèt:'''
 $1",
 'revdel-restore' => 'Ngowahi visiblitas (pangatonan)',
+'revdel-restore-deleted' => 'revisi kabusak',
+'revdel-restore-visible' => 'revisi kétok',
 'pagehist' => 'Sajarah kaca',
 'deletedhist' => 'Sajarah sing dibusak',
 'revdelete-hide-current' => 'Gagal ndhelikaké révisi tanggal $2, $1: iki arupa révisi paling anyar.
@@ -826,7 +829,7 @@ Panjenengan ora nduwèni aksès menyang révisi iki.',
 'revdelete-no-change' => "'''Pènget:''' révisi tanggal $1, jam $2 wis nduwèni aturan pandhelikan kasebut.",
 'revdelete-concurrent-change' => 'Gagal ngowahi révisi tanggal $1, jam $2: statusé mbokmanawa wis diowahi déning panganggo liya bebarengan karo panjenengan.
 Mangga priksa cathetan log.',
-'revdelete-only-restricted' => 'Panjenengan ora bisa ndhelikaké révisi-révisi iki saka pangurus tanpa milih uga salah siji opsi pandhelikan liyané.',
+'revdelete-only-restricted' => 'Ora bisa ndhelikaké siji barang mawa tanggal $1 wanci $2: Sampéyan ora bisa ndhelikaké barang kuwi saka pangurus tanpa milih salah sawiji pilihan kanggo ndhelikaké sing liyané.',
 'revdelete-otherreason' => 'Alesan liya/tambahan:',
 'revdelete-reasonotherlist' => 'Alesan liya',
 'revdelete-edit-reasonlist' => 'Sunting alesan pambusakan',
@@ -834,7 +837,8 @@ Mangga priksa cathetan log.',
 
 # Suppression log
 'suppressionlog' => "Log barang-barang sing didelikaké (''oversight'')",
-'suppressionlogtext' => "Ing ngisor iki kapacak daftar pambusakan lan pamblokiran pungkasan sing uga nyangkut isi sing didelikaké saka para opsis. Mangga mirsani [[Special:IPBlockList|daftar pamblokiran IP]] kanggo daftar pambuwangan (''ban'') lan pamblokiran sing saiki lagi operasional.",
+'suppressionlogtext' => 'Ngisor iki daptar apa-apa waé sing wis dibusak lan diblokir kalebu kontèn sing didhelikaké saka para pangurus.
+Delok [[Special:BlockList|daptar blokiran]] sing isiné daptar apa-apa waé sing lagi dilarang lan diblokir.',
 
 # History merging
 'mergehistory' => 'Gabung sejarah kaca',
@@ -867,13 +871,13 @@ Gunakna tombol radio kanggo nggabungaké révisi-révisi sing digawé sadurungé
 'mergelogpagetext' => 'Ing ngisor iki kapacak daftar panggabungan sajarah kaca ing kaca liyané.',
 
 # Diffs
-'history-title' => 'Sajarah revisi saka "$1"',
+'history-title' => 'Riwayat rèvisi saka "$1"',
 'difference-title' => '$1: Bèntenipun revisi',
 'lineno' => 'Larikan $1:',
 'compareselectedversions' => 'Bandhingna vèrsi kapilih',
 'showhideselectedversions' => 'Tampilaké/dhelikaké révisi kapilih',
 'editundo' => 'batalna',
-'diff-multi' => '({{PLURAL:$1|Sawiji|$1}} revisi antara sing ora dituduhaké.)',
+'diff-multi' => '({{PLURAL:$1Siji rèvisi sedhengan|$1 rèvisi sedhengan}} déning {{PLURAL:$2|sak panganggo|$2 panganggo}} ora dituduhaké)',
 
 # Search results
 'searchresults' => 'Kasil panggolèkan',
index 1552de6..2907ef1 100644 (file)
@@ -150,7 +150,7 @@ $linkTrail = '/^([a-zაბგდევზთიკლმნოპჟრსტ
 
 $messages = array(
 # User preference toggles
-'tog-underline' => 'á\83®á\83\90á\83\96á\83\98 á\83\92á\83\90á\83£á\83¡á\83\95á\83\98 á\83\91á\83\9bá\83£á\83\9aá\83\94á\83\91á\83¡:',
+'tog-underline' => 'á\83\91á\83\9bá\83£á\83\9aá\83\94á\83\91á\83\98á\83¡ á\83®á\83\90á\83\96á\83\92á\83\90á\83¡á\83\9bá\83\90:',
 'tog-justify' => 'გაასწორე პარაგრაფები',
 'tog-hideminor' => 'დამალე მცირე რედაქტირება ბოლო ცვლილებებში',
 'tog-hidepatrolled' => 'დამალეთ შესწორებული რედაქტირებები ბოლო ცვლილებებში',
@@ -571,6 +571,7 @@ $2',
 'remembermypassword' => 'დამიმახსოვრე ამ კომპიუტერზე (მაქსიმუმ $1 {{PLURAL:$1|დღე}})',
 'securelogin-stick-https' => 'გააგრძელეთ კავშირი HTTPS-თან შესვლის შემდეგ',
 'yourdomainname' => 'თქვენი დომენი',
+'password-change-forbidden' => 'თქვენ არ შეგიძლიათ ამ ვიკიში პაროლის შეცვლა.',
 'externaldberror' => 'საგარეო მონაცემთა ბაზაში აუტენტიფიკაციის შეცდომაა, ან თქვენ არ გაქვთ საკმარისი უფლებები საგარეო ანგარიშში ცვლილებების შესატანად.',
 'login' => 'შესვლა',
 'nav-login-createaccount' => 'შესვლა / რეგისტრაცია',
@@ -1780,7 +1781,7 @@ $1',
 'listfiles_search_for' => 'ძიება სურათის სახელის მიხედვით:',
 'imgfile' => 'ფაილი',
 'listfiles' => 'სურათების სია',
-'listfiles_thumb' => 'მინიატურა',
+'listfiles_thumb' => 'á\83\9bá\83\98á\83\9cá\83\98á\83\90á\83¢á\83\98á\83£á\83 á\83\90',
 'listfiles_date' => 'თარიღი',
 'listfiles_name' => 'სახელი',
 'listfiles_user' => 'მომხმარებელი',
@@ -1797,7 +1798,7 @@ $1',
 'filehist-revert' => 'დააბრუნე',
 'filehist-current' => 'მიმდინარე',
 'filehist-datetime' => 'თარიღი/დრო',
-'filehist-thumb' => 'მინიატურა',
+'filehist-thumb' => 'á\83\9bá\83\98á\83\9cá\83\98á\83\90á\83¢á\83\98á\83£á\83 á\83\90',
 'filehist-thumbtext' => 'მინიატურა $1 ვერსიისთვის',
 'filehist-nothumb' => 'არ არის მინიატურა',
 'filehist-user' => 'მომხმარებელი',
@@ -1909,9 +1910,9 @@ $1',
 
 'disambiguations' => 'გვერდები, რომელთაც აქვთ ბმული მრავალმნიშვნელოვან გვერდებზე',
 'disambiguationspage' => 'Template:მრავალმნიშვნელოვანი',
-'disambiguations-text' => "á\83¨á\83\94á\83\9bá\83\93á\83\94á\83\92á\83\98 á\83\92á\83\95á\83\94á\83 á\83\93á\83\94á\83\91á\83\98 á\83\93á\83\90á\83\99á\83\90á\83\95á\83¨á\83\98á\83 á\83\94á\83\91á\83£á\83\9aá\83\94á\83\91á\83\98 á\83\90á\83 á\83\98á\83\90á\83\9c '''á\83\9bá\83 á\83\90á\83\95á\83\90á\83\9aá\83\9bá\83\9cá\83\98á\83¨á\83\95á\83\9cá\83\94á\83\9aá\83\9dá\83\91á\83\98á\83¡ á\83\92á\83\95á\83\94á\83 á\83\93á\83\97á\83\90á\83\9c'''.
-ამის ნაცვლად იგი უნდა შეესაბამოს კონკრეტულ სტატიას.<br />
\83\92á\83\95á\83\94á\83 á\83\93á\83\98 á\83\98á\83\97á\83\95á\83\9aá\83\94á\83\91á\83\90 á\83\9bá\83 á\83\90á\83\95á\83\90á\83\9aá\83\9bá\83\9cá\83\98á\83¨á\83\95á\83\9cá\83\94á\83\9aá\83\9dá\83\95á\83\9cá\83\90á\83\93, á\83\97á\83£ á\83\9bá\83\90á\83¡á\83\96á\83\94 á\83\92á\83\90á\83\9bá\83\9dá\83§á\83\94á\83\9cá\83\94á\83\91á\83£á\83\9aá\83\98á\83\90 [[MediaWiki:Disambiguationspage|á\83\9bá\83\98á\83\97á\83¡á\83\90á\83\97á\83\98á\83\97á\83\94á\83\91á\83\94á\83\9aá\83\98 á\83\97á\83\90á\83 á\83\92á\83\98]].",
+'disambiguations-text' => "á\83¨á\83\94á\83\9bá\83\93á\83\94á\83\92á\83\98 á\83\92á\83\95á\83\94á\83 á\83\93á\83\94á\83\91á\83\98 á\83¨á\83\94á\83\98á\83ªá\83\90á\83\95á\83\94á\83\9c á\83¡á\83£á\83\9a á\83\9bá\83ªá\83\98á\83 á\83\94 á\83\94á\83 á\83\97 á\83\91á\83\9bá\83£á\83\9aá\83¡ '''á\83\9bá\83 á\83\90á\83\95á\83\90á\83\9aá\83\9bá\83\9cá\83\98á\83¨á\83\95á\83\9cá\83\94á\83\9aá\83\9dá\83\91á\83\98á\83¡ á\83\92á\83\95á\83\94á\83 á\83\93á\83\96á\83\94'''.
+ამის ნაცვლად, სავარაუდოდ, ისისნი უნდა მიუთითებდნენ შესაბამის კონკრეტულ სტატიაზე.<br />
\83\92á\83\95á\83\94á\83 á\83\93á\83\98 á\83\98á\83\97á\83\95á\83\9aá\83\94á\83\91á\83\90 á\83\9bá\83 á\83\90á\83\95á\83\90á\83\9bá\83\9cá\83\98á\83¨á\83\95á\83\9cá\83\94á\83\9aá\83\9dá\83\95á\83\9cá\83\90á\83\93, á\83\97á\83£ á\83\9bá\83\90á\83¡á\83¨á\83\98 á\83\92á\83\90á\83\9cá\83\97á\83\90á\83\95á\83¡á\83\94á\83\91á\83£á\83\9aá\83\98á\83\90 á\83\97á\83\90á\83 á\83\92á\83\98, á\83 á\83\9dá\83\9bá\83\9aá\83\98á\83¡ á\83¡á\83\90á\83®á\83\94á\83\9aá\83\98á\83ª á\83\9bá\83\98á\83\97á\83\98á\83\97á\83\94á\83\91á\83£á\83\9aá\83\98á\83\90 á\83\92á\83\95á\83\94á\83 á\83\93á\83\96á\83\94 [[MediaWiki:Disambiguationspage]].",
 
 'doubleredirects' => 'ორმაგი გადამისამართება',
 'doubleredirectstext' => 'ამ გვერდზე ჩამოთვლილია გვერდები, რომლებიც გადამისამართებულია სხვა გადამისამართების გვერდებზე.
@@ -3673,7 +3674,7 @@ $5
 'version-api' => 'API',
 'version-other' => 'სხვა',
 'version-mediahandlers' => 'მედია დამუშავება',
-'version-hooks' => 'ჰუკებш',
+'version-hooks' => 'ჰუკებ',
 'version-extension-functions' => 'გაფართოებათა ფუნქციები',
 'version-parser-extensiontags' => 'სინტაქსური ანალიზატორის თეგი',
 'version-parser-function-hooks' => 'სინტაქსური ანალიზატორის ჰუკი',
@@ -3895,5 +3896,5 @@ MediaWiki ვრცელდება იმ იმედით, რომ ი
 'duration-millennia' => '$1 {{PLURAL:$1|ათასწლეული|ათასწლეული}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|á\83\9bá\83\98á\83£á\83¦á\83\94á\83\91á\83\94á\83\9aá\83\98 á\83¤á\83\90á\83\98á\83\9aá\83\98á\83¡ á\83¢á\83\98á\83\9eá\83\98á\83\90\83\9bá\83\98á\83£á\83¦á\83\94á\83\91á\83\94á\83\9aá\83\98 á\83¤á\83\90á\83\98á\83\9aá\83\98á\83¡ á\83¢á\83\98á\83\9eá\83\98á\83\90}}. á\83\9bá\83\98á\83¦á\83\94á\83\91á\83£á\83\9aი ფაილის {{PLURAL:$3|ტიპია|ტიპებია}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|á\83\9bá\83\98á\83£á\83¦á\83\94á\83\91á\83\94á\83\9aá\83\98 á\83¤á\83\90á\83\98á\83\9aá\83\98á\83¡ á\83¢á\83\98á\83\9eá\83\98á\83\90\83\9bá\83\98á\83£á\83¦á\83\94á\83\91á\83\94á\83\9aá\83\98 á\83¤á\83\90á\83\98á\83\9aá\83\98á\83¡ á\83¢á\83\98á\83\9eá\83\94á\83\91á\83\98á\83\90}}. á\83\9bá\83\98á\83¡á\83\90á\83¦á\83\94á\83\91á\83\98ი ფაილის {{PLURAL:$3|ტიპია|ტიპებია}} $2.',
 );
index fe97fb1..94f15f0 100644 (file)
@@ -11,6 +11,7 @@
  * @author Agzennay
  * @author Azwaw
  * @author Mmistmurt
+ * @author MoubarikBelkasim
  * @author Teak
  * @author Urhixidur
  */
@@ -654,7 +655,7 @@ Inedbalen wiyaḍ deg wiki-yagi zemren ad ẓren imuren i yettwafren u zemren a
 'revertmerge' => 'Fru',
 
 # Diffs
-'history-title' => 'Amazray n allasen n "$1"',
+'history-title' => 'Tiẓṛi tiss sint umezruy n "$1"',
 'lineno' => 'Ajerriḍ $1:',
 'compareselectedversions' => 'Ẓer imgerraden ger tisiwal i textareḍ',
 'editundo' => 'ssefsu',
index 9cef385..a56f7d3 100644 (file)
@@ -602,6 +602,10 @@ $1',
 'youhavenewmessages' => '다른 사용자가 $1란에 글을 남겼습니다. ($2)',
 'newmessageslink' => '사용자 토론',
 'newmessagesdifflink' => '바뀐 내용 비교',
+'youhavenewmessagesfromusers' => '{{PLURAL:$3|다른 사용자|$3 사용자}}가 $1란에 글을 남겼습니다. ($2)',
+'youhavenewmessagesmanyusers' => '여러 사용자가 $1란에 글을 남겼습니다. ($2)',
+'newmessageslinkplural' => '{{PLURAL:$1|새 메시지}}',
+'newmessagesdifflinkplural' => '최근 {{PLURAL:$1|바뀜}}',
 'youhavenewmessagesmulti' => '다른 사용자가 $1란에 글을 남겼습니다.',
 'editsection' => '편집',
 'editold' => '편집',
@@ -1148,6 +1152,7 @@ $2개 보다 적게 써야 하지만 지금은 $1개를 쓰고 있습니다.",
 'expansion-depth-exceeded-warning' => '페이지가 확장 깊이를 초과하였습니다',
 'parser-unstrip-loop-warning' => '스트립하지 않는 반복이 감지되었습니다',
 'parser-unstrip-recursion-limit' => '스트립하지 않는 재귀 한도가 초과됨 ($1)',
+'converter-manual-rule-error' => '언어 변환 규칙을 수동으로 지정하는 도중 오류',
 
 # "Undo" feature
 'undo-success' => '편집을 되돌릴 수 있습니다.
index 34229ea..58bae9e 100644 (file)
@@ -588,6 +588,9 @@ Los tus trokamientos no se tienen guadrados!",
 'hiddencategories' => 'Esta hoja es un miembro de {{PLURAL:$1|1 kateggoría escondida|$1 kateggorías escondidas}}:',
 'nocreate-loggedin' => 'No tienes el permisso de creas hojas nuevas.',
 'permissionserrorstext-withaction' => 'No tienes el permiso para $2, por las {{PLURAL:$1|razón|razones}} venideras:',
+'recreate-moveddeleted-warn' => "'''Aviso: Estas kriando una oja la kuala fue efassada antes.'''
+Kale ke penses si es menesterozo editar esta oja.
+El enrejistro de efassado i taxireado para esta oja puede ser meldado aki:",
 'moveddeleted-notice' => "Esta ója fue efassada.
 El ''log'' de efassado i taxireado de la ója es amostrado abasho para dar referensia.",
 
@@ -595,6 +598,9 @@ El ''log'' de efassado i taxireado de la ója es amostrado abasho para dar refer
 'post-expand-template-inclusion-warning' => "'''Avizo:''' La contenencia de xablon está muy grande.
 Algunos xablones no van á ser comprendidos.",
 'post-expand-template-inclusion-category' => 'Hojas ande la contenencia de xablones está sovrepassada',
+'post-expand-template-argument-warning' => "'''Aviso:''' Esta oja tiene kuanto menos un kampo enel xablon muy lungo.
+Este o estos kampos no van ser amostrados",
+'post-expand-template-argument-category' => 'Ojas ke tienen xablones kon parametros no uzados',
 
 # History pages
 'viewpagelogs' => 'Ver los registros de esta hoja',
@@ -649,6 +655,7 @@ Leyenda: (act) = diferencias con la versión actual,
 'lineno' => 'Shurá $1:',
 'compareselectedversions' => 'Comparar versiones escogidas',
 'editundo' => 'deshaze',
+'diff-multi' => '(No {{PLURAL:$1|es amostrado un trokamiento intermedio echo|son amostrados $1 trokamientos intermedios echos}} por {{PLURAL:$2|un usador|$2 usadores}})',
 
 # Search results
 'searchresults' => 'Resultados de la búsqueda',
@@ -678,6 +685,7 @@ Leyenda: (act) = diferencias con la versión actual,
 'searchprofile-everything-tooltip' => 'Buscar en todo el contènido (y también hojas de diskusyón)',
 'searchprofile-advanced-tooltip' => 'Buscar en espacios de nombres particůlares',
 'search-result-size' => '$1 ({{PLURAL:$2|1 biervo|$2 biervos}})',
+'search-result-category-size' => '{{PLURAL:$1|1 miembro|$1 miembros}} ({{PLURAL:$2|1 basho-kateggoria|$2 basho-kateggoria}}, {{PLURAL:$3|1 dossia|$3 dossias}})',
 'search-redirect' => '(direksión desde $1)',
 'search-section' => '(capítůlo $1)',
 'search-suggest' => 'Quisites dezir: $1',
@@ -728,6 +736,8 @@ Las búsquedas producen más o munco a buscar biervos comunes como «la» o «de
 'yourlanguage' => 'Lingua:',
 'yournick' => 'Firma mueva:',
 'email' => 'Letral',
+'prefs-help-email' => 'El adreso de e-posta es menester para alimpiar la tu parola, si la olvidates',
+'prefs-help-email-others' => 'Endemas puedes eskojer si keres dar pueder a otros usadores de azer kontakto kon ti por modre de e-posta, a  traverso de un atamiento en tus ojas de usador i de diskusyon.',
 'prefs-signature' => 'Firma',
 
 # Groups
@@ -762,6 +772,7 @@ Las búsquedas producen más o munco a buscar biervos comunes como «la» o «de
 'recentchanges-label-bot' => 'Este trokamiento fue echo por un bot',
 'recentchanges-label-unpatrolled' => 'Estre trokamiento no esta akavidado',
 'rcnote' => "Debaxo {{PLURAL:$1|ay '''1''' trocamiento realizado|están los dal cabo '''$1''' trocamientos realizados}} en  {{PLURAL:$2|el dal cabo día|los dal cabo '''$2''' días}}, hasta el $4, $5.",
+'rcnotefrom' => "Debasho se amostran los trokamientos desde '''$2''' (amostrados fina <b>$1</b>)",
 'rclistfrom' => 'Mostra los trocamientos nuevos empeçando desde $1',
 'rcshowhideminor' => '$1 trocamientos chiquiticos',
 'rcshowhidebots' => '$1 bots',
@@ -832,10 +843,13 @@ La descripción en su [$2 hoja de descripción del arxivo] está amostrada debax
 # Statistics
 'statistics' => 'Estatísticas',
 
+'disambiguationspage' => 'Template:Aklarasion',
+
 # Miscellaneous special pages
 'nbytes' => '$1 {{PLURAL:$1|bayt|baytes}}',
 'nmembers' => '$1 {{PLURAL:$1|miembro|miembros}}',
 'prefixindex' => 'Todas las hojas con prefixo',
+'usercreated' => '{{GENDER:$3|Enrejistrado|Enrejistrada}} el $1 a las $2',
 'newpages' => 'Hojas muevas',
 'ancientpages' => 'Artikolos mas viejos',
 'move' => 'taxirea',
@@ -966,6 +980,7 @@ A continuación se mostran las opciones actuales de la hoja '''$1''':",
 'sp-contributions-newbies' => 'Mostrar solo las ajustamientos de los usuarios nuevos',
 'sp-contributions-blocklog' => 'registro de bloqueos',
 'sp-contributions-uploads' => 'suvidas',
+'sp-contributions-logs' => 'enrejistros',
 'sp-contributions-talk' => 'Diskusyón',
 'sp-contributions-search' => 'Buscar ajustamientos',
 'sp-contributions-username' => 'Dirección IP o nombre de usuario:',
@@ -1218,6 +1233,9 @@ Los otros campos se van a guardar por defecto.
 'watchlisttools-edit' => 'Ver i trocar tu lista de escogidas',
 'watchlisttools-raw' => 'Troca tu lista de escogidas en crudo',
 
+# Core parser functions
+'duplicate-defaultsort' => '\'\'\'Aviso:\'\'\' la klave primaria para ordenamiento "$2" anula la primera "$1"',
+
 # Special:Version
 'version' => 'Versión',
 'version-specialpages' => 'Pajinas espesiales',
@@ -1237,6 +1255,16 @@ Los otros campos se van a guardar por defecto.
 'specialpages' => 'Hojas especiales',
 'specialpages-group-users' => 'Usadores y derechos',
 
+# External image whitelist
+'external_image_whitelist' => ' #Desha esta linea ansina komo esta<pre>
+#Mete partes de frasas (solo la parte ke va entre los //) enbasho
+#Eyas van ser komparadas kon las URLs de las dossias ekternas (hotlinked)
+#Akeyos iguales van ser amostrados komo una imej; si no, solo el su atamientoque 
+#Las lineas ke empiezan kor «#» son konsideradas komentarios
+#Esta no aze diferente el senso se la letra
+
+#Mete todas las partes de frasas regex enriva de esta linea. Desha esta ansina komo se topa</pre>',
+
 # Special:Tags
 'tag-filter' => 'Filtro de [[Special:Tags|etiquetas]]:',
 'tag-filter-submit' => 'Filtro',
index 4421868..bc34ed0 100644 (file)
@@ -441,6 +441,10 @@ Kuckt d'[[Special:Version|Versiounssäit]].",
 'youhavenewmessages' => 'Dir hutt $1 ($2).',
 'newmessageslink' => 'nei Messagen',
 'newmessagesdifflink' => 'Lescht Ännerung',
+'youhavenewmessagesfromusers' => 'Dir hutt $1 vu(n) {{PLURAL:$3|engem anere Benotzer|$3 anere Benotzer}} ($2).',
+'youhavenewmessagesmanyusers' => 'Dir hutt $1 vu ville Benotzer ($2)',
+'newmessageslinkplural' => '{{PLURAL:$1|een neie Message|nei Message}}',
+'newmessagesdifflinkplural' => 'lescht {{PLURAL:$1|Ännerung|Ännerungen}}',
 'youhavenewmessagesmulti' => 'Dir hutt nei Messagen op $1',
 'editsection' => 'änneren',
 'editold' => 'änneren',
index 1f6799d..7b8431d 100644 (file)
@@ -315,7 +315,7 @@ $1',
 'editold' => 'labot',
 'viewsourceold' => 'aplūkot kodu',
 'editlink' => 'labot',
-'viewsourcelink' => 'Skatīt pirmkodu',
+'viewsourcelink' => 'aplūkot kodu',
 'editsectionhint' => 'Rediģēt sadaļu: $1',
 'toc' => 'Satura rādītājs',
 'showtoc' => 'parādīt',
index 2114e4a..ccefdc1 100644 (file)
@@ -597,6 +597,10 @@ $1',
 'youhavenewmessages' => 'Имате $1 ($2).',
 'newmessageslink' => 'нови пораки',
 'newmessagesdifflink' => 'скорешна промена',
+'youhavenewmessagesfromusers' => 'Имате $1 од {{PLURAL:$3|еден корисник|$3 корисници}} ($2).',
+'youhavenewmessagesmanyusers' => 'Имате $1 од многу корисници ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|нова порака|нови пораки}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|последна промена|последни промени}}',
 'youhavenewmessagesmulti' => 'Имате нови пораки на $1',
 'editsection' => 'уреди',
 'editsection-brackets' => '[$1]',
@@ -1002,13 +1006,13 @@ $2
 Затоа мораме да ја користиме неговата бројчена IP-адреса за да го препознаеме.
 Една ваква IP-адреса може да ја делат повеќе корисници.
 Ако сте анонимен корисник и сметате дека кон вас се упатени нерелевантни коментари, тогаш [[Special:UserLogin/signup|создајте корисничка сметка]] или [[Special:UserLogin|најавете се]] за да избегнете поистоветување со други анонимни корисници во иднина.''",
-'noarticletext' => 'Ð\9cоменÑ\82ално Ð½ÐµÐ¼Ð° Ñ\82екÑ\81Ñ\82 Ð½Ð° Ð¾Ð²Ð°Ð° Ñ\81Ñ\82Ñ\80аниÑ\86а.
\9cожеÑ\82е Ð´Ð° Ð½Ð°Ð¿Ñ\80авиÑ\82е [[Special:Search/{{PAGENAME}}|пÑ\80ебаÑ\80Ñ\83ваÑ\9aе Ð·Ð° Ð¾Ð²Ð¾Ñ\98 Ð½Ð°Ñ\81лов Ð½Ð° Ñ\81Ñ\82Ñ\80аниÑ\86а]] Ð²Ð¾ Ð´Ñ\80Ñ\83ги Ñ\81Ñ\82Ñ\80аниÑ\86и,
-<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} пребарување во дневниците],
-или [{{fullurl:{{FULLPAGENAME}}|action=edit}} да ја уредите оваа страница]</span>.',
-'noarticletext-nopermission' => 'Ð\9dема Ñ\82екÑ\81Ñ\82 Ð½Ð° Ð¾Ð²Ð°Ð° Ñ\81Ñ\82Ñ\80аниÑ\86а.
\9cожеÑ\82е Ð´Ð° Ð³Ð¾ [[Special:Search/{{PAGENAME}}|пÑ\80ебаÑ\80аÑ\82е Ð¾Ð²Ð¾Ñ\98 Ð½Ð°Ñ\81лов]] Ð²Ð¾ Ð´Ñ\80Ñ\83ги Ñ\81Ñ\82Ñ\80аниÑ\86и,
-или да ги <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} пребарате соодветните дневници]</span>.',
+'noarticletext' => 'Таква Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81è Ñ\83Ñ\88Ñ\82е Ð½Ðµ Ð¿Ð¾Ñ\81Ñ\82ои.
\9cожеÑ\82е Ð´Ð° Ð¿Ñ\80овеÑ\80иÑ\82е [[Special:Search/{{PAGENAME}}|дали Ð½Ð°Ñ\81ловоÑ\82 Ñ\81е Ñ\81поменÑ\83ва]] Ð²Ð¾ Ð´Ñ\80Ñ\83ги Ñ\81Ñ\82аÑ\82ии,
+да ги <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} пребарате дневниците],
+или да [{{fullurl:{{FULLPAGENAME}}|action=edit}} ја создадете]</span>.',
+'noarticletext-nopermission' => 'Таква Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81è Ñ\83Ñ\88Ñ\82е Ð½Ðµ Ð¿Ð¾Ñ\81Ñ\82ои.
\9cожеÑ\82е Ð´Ð° Ð¿Ñ\80овеÑ\80иÑ\82е [[Special:Search/{{PAGENAME}}|дали Ð½Ð°Ñ\81ловоÑ\82 Ñ\81е Ñ\81поменÑ\83ва]] Ð²Ð¾ Ð´Ñ\80Ñ\83ги Ñ\81Ñ\82аÑ\82ии,
+или да ги <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} пребарате дневниците]</span>.',
 'missing-revision' => 'Не ја пронајдов ревизијата бр. $1 на страницата со наслов „{{PAGENAME}}“.
 
 Ова обично се должи на застарена врска за разлики што води кон избришана страница.
@@ -1143,6 +1147,7 @@ $2
 'expansion-depth-exceeded-warning' => 'Страницата ја надмина длабочината на проширувањето',
 'parser-unstrip-loop-warning' => 'Утврдена е јамка',
 'parser-unstrip-recursion-limit' => 'Пречекорена е границата на рекурзија ($1)',
+'converter-manual-rule-error' => 'Пронајдов грешка во правилото за рачно претворање на јазик',
 
 # "Undo" feature
 'undo-success' => 'Уредувањето може да се откаже.
@@ -2653,7 +2658,7 @@ $1',
 'whatlinkshere' => 'Што води овде',
 'whatlinkshere-title' => 'Страници со врски што водат до „$1“',
 'whatlinkshere-page' => 'Страница:',
-'linkshere' => "СледниÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86и Ð¸Ð¼Ð°Ð°Ñ\82 Ð²Ñ\80Ñ\81ка Ð´Ð¾ '''[[:$1]]''':",
+'linkshere' => "СледниÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86и Ð²Ð¾Ð´Ð°Ñ\82 ÐºÐ¾Ð½ â\80\9e'''[[:$1]]'''â\80\9c:",
 'nolinkshere' => "Нема страници што водат кон '''[[:$1]]'''.",
 'nolinkshere-ns' => "Нема страници што водат кон '''[[:$1]]''' во избраниот именски простор.",
 'isredirect' => 'пренасочувачка страница',
index 179be91..574e3cb 100644 (file)
@@ -590,6 +590,10 @@ $1',
 'youhavenewmessages' => 'താങ്കൾക്ക് $1 ഉണ്ട് ($2).',
 'newmessageslink' => 'പുതിയ സന്ദേശങ്ങൾ',
 'newmessagesdifflink' => 'അവസാന മാറ്റം',
+'youhavenewmessagesfromusers' => 'താങ്കൾക്ക് {{PLURAL:$3|മറ്റൊരു ഉപയോക്താവ്|മറ്റ് $3 ഉപയോക്താക്കൾ}} $1 ചേർത്തിട്ടുണ്ട് ($2).',
+'youhavenewmessagesmanyusers' => 'താങ്കൾക്ക് പലർ $1 ചേർത്തിട്ടുണ്ട് ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|പുതിയ സന്ദേശം|പുതിയ സന്ദേശങ്ങൾ}}',
+'newmessagesdifflinkplural' => 'അവസാന {{PLURAL:$1|മാറ്റം|മാറ്റങ്ങൾ}}',
 'youhavenewmessagesmulti' => 'താങ്കൾക്ക് $1 താളിൽ പുതിയ സന്ദേശങ്ങൾ ഉണ്ട്',
 'editsection' => 'തിരുത്തുക',
 'editold' => 'തിരുത്തുക',
index 41fec30..ce150a4 100644 (file)
@@ -460,6 +460,10 @@ $1',
 'youhavenewmessages' => 'Anda mempunyai $1 ($2).',
 'newmessageslink' => 'pesanan baru',
 'newmessagesdifflink' => 'perubahan terakhir',
+'youhavenewmessagesfromusers' => 'Anda menerima $1 daripada {{PLURAL:$3|seorang|$3 orang}} pengguna lain ($2).',
+'youhavenewmessagesmanyusers' => 'Anda menerima $1 daripada ramai pengguna ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|pesanan|pesanan-pesanan}} baru',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|perubahan|perubahan-perubahan}} terkini',
 'youhavenewmessagesmulti' => 'Anda telah menerima pesanan baru pada $1',
 'editsection' => 'sunting',
 'editsection-brackets' => '[$1]',
@@ -608,6 +612,7 @@ Akaun anda telah dibuka. Jangan lupa untuk mengubah [[Special:Preferences|keutam
 'remembermypassword' => 'Ingat log masuk saya pada pelayar ini (tidak melebihi $1 {{PLURAL:$1|hari|hari}})',
 'securelogin-stick-https' => 'Kekal disambungkan ke HTTPS selepas log masuk',
 'yourdomainname' => 'Domain anda:',
+'password-change-forbidden' => 'Anda tidak dapat mengubah kata laluan di wiki ini.',
 'externaldberror' => 'Berlaku ralat pangkalan data bagi pengesahan luar atau anda tidak dibenarkan mengemaskinikan akaun luar anda.',
 'login' => 'Log masuk',
 'nav-login-createaccount' => 'Log masuk / buka akaun',
@@ -842,6 +847,10 @@ Sekiranya anda adalah seorang pengguna tanpa nama dan berasa bahawa komen yang t
 'noarticletext-nopermission' => 'Tiada teks dalam laman ini ketika ini.
 Anda boleh [[Special:Search/{{PAGENAME}}|mencari tajuk laman ini]] dalam laman lain,
 atau <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mencari log yang berkaitan]</span>.',
+'missing-revision' => 'Semakan #$1 pada halaman "{{PAGENAME}}" tidak wujud.
+
+Hal ini biasanya disebabkan oleh pautan sejarah yang lapuk ke halaman yang sudah dihapuskan.
+Butirannya boleh didapati di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log penghapusan].',
 'userpage-userdoesnotexist' => 'Akaun pengguna "<nowiki>$1</nowiki>" tidak berdaftar. Sila pastikan sama ada anda mahu mencipta/menyunting laman ini.',
 'userpage-userdoesnotexist-view' => 'Akaun pengguna "$1" tidak berdaftar.',
 'blocked-notice-logextract' => 'Pengguna ini sedang disekat.
@@ -965,6 +974,7 @@ Argumen-argumen ini telah ditinggalkan.',
 'expansion-depth-exceeded-warning' => 'Laman terlebih dalam peluasan',
 'parser-unstrip-loop-warning' => 'Gelung unstrip dikesan',
 'parser-unstrip-recursion-limit' => 'Had rekursi unstrip dilampaui ($1)',
+'converter-manual-rule-error' => 'Ralat dikesan dalam aturan penukaran bahasa manual',
 
 # "Undo" feature
 'undo-success' => 'Suntingan ini boleh dibatalkan. Sila semak perbandingan di bawah untuk mengesahkan bahawa anda betul-betul mahu melakukan tindakan ini, kemudian simpan perubahan tersebut.',
@@ -1153,6 +1163,10 @@ Sila pastikan bahawa perubahan ini akan mengekalkan kesinambungan sejarah laman.
 'editundo' => 'batal',
 'diff-multi' => '($1 {{PLURAL:$1|semakan pertengahan|semakan pertengahan}} oleh $2 {{PLURAL:$2|pengguna|pengguna}} tidak dipaparkan)',
 'diff-multi-manyusers' => '($1 {{PLURAL:$1|semakan pertengahan|semakan pertengahan}} oleh lebih daripada $2 {{PLURAL:$2|pengguna|pengguna}} tidak dipaparkan)',
+'difference-missing-revision' => '{{PLURAL:$2|Satu semakan|$2 semakan}} bagi perbezaan ini ($1) tidak ditemui.
+
+Hal ini biasanya disebabkan oleh pautan perbezaan yang lapuk ke halaman yang sudah dihapuskan.
+Butirannya boleh didapati di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log penghapusan].',
 
 # Search results
 'searchresults' => 'Hasil carian',
@@ -1927,7 +1941,9 @@ Mungkin anda ingin menyunting keterangan pada [$2 laman penerangan failnya] di s
 
 'disambiguations' => 'Laman-laman yang berpaut dengan laman penyahkekaburan',
 'disambiguationspage' => 'Template:disambig',
-'disambiguations-text' => "Laman-laman berikut mengandungi pautan ke '''laman penyahtaksaan'''. Pautan ini sepatutnya ditujukan kepada topik yang sepatutnya.<br />Sesebuah laman dianggap sebagai laman penyahtaksaan jika ia menggunakan templat yang dipaut dari [[MediaWiki:Disambiguationspage]]",
+'disambiguations-text' => "Laman-laman berikut mengandungi sekurang-kurangnya satu pautan ke '''laman penyahkekaburan'''.
+Pautan ini sepatutnya ditujukan ke topik yang sepatutnya.<br />
+Sesebuah laman dianggap sebagai laman penyahkekaburan jika ia menggunakan templat yang dipaut dari [[MediaWiki:Disambiguationspage]]",
 
 'doubleredirects' => 'Lencongan berganda',
 'doubleredirectstext' => 'Yang berikut ialah senarai laman yang melencong ke laman lencongan lain. Setiap baris mengandungi pautan ke laman lencongan pertama dan kedua, serta baris pertama bagi teks lencongan kedua, lazimnya merupakan laman sasaran "sebenar", yang sepatutnya ditujui oleh lencongan pertama.
@@ -3852,5 +3868,5 @@ Ataupun, anda boleh menggunakan borang yang mudah di bawah. Ulasan anda akan dic
 'duration-millennia' => '$1 alaf',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 adalah {{PLURAL:$4|jenis|jenis-jenis}} fail yang dilarang. {{PLURAL:$3|Jenis|Jenis-jenis}} fail yang dibenarkan ialah $2.',
+'api-error-filetype-banned-type' => '$1 merupakan {{PLURAL:$4|jenis|jenis-jenis}} fail yang dilarang. {{PLURAL:$3|Jenis|Jenis-jenis}} fail yang dibenarkan ialah $2.',
 );
index a1e2afa..e74ddf7 100644 (file)
@@ -279,7 +279,7 @@ $messages = array(
 'tog-hidepatrolled' => 'Aħbi l-modifiki verifikati fit-tibdil riċenti',
 'tog-newpageshidepatrolled' => 'Aħbi l-paġni verifikati mil-lista tal-paġni l-ġodda',
 'tog-extendwatchlist' => "Espandi l-lista ta' osservazzjoni biex turi t-tibdil kollu, u mhux biss dak riċenti",
-'tog-usenewrc' => 'Uża t-tibdil riċenti avvanzat (bżonn tal-JavaScript)',
+'tog-usenewrc' => "Iġbor il-modifiki skont il-paġna fit-tibdil riċenti u fil-lista ta' osservazzjonijiet (bżonn tal-Javascript)",
 'tog-numberheadings' => 'Numerazzjoni awtomatika tat-titli tas-sezzjonijiet',
 'tog-showtoolbar' => 'Uri l-kolonna tal-għodda għall-immodifikar (bżonn tal-JavaScript)',
 'tog-editondblclick' => "Immodifika l-paġni permezz ta' klikk doppju (bżonn tal-JavaScript)",
@@ -287,8 +287,8 @@ $messages = array(
 'tog-editsectiononrightclick' => "L-immodifikar ta' sezzjonijiet bi klikk lemini fuq it-titli tas-sezzjonijiet (bżonn tal-JavaScript)",
 'tog-showtoc' => "Uri l-werrej (għal paġni b'iktar minn 3 sezzjonijiet)",
 'tog-rememberpassword' => "Ftakar il-login tiegħi fuq dan il-browżer (għal massimu ta' {{PLURAL:$1|ġurnata|$1 ġurnata}})",
-'tog-watchcreations' => "Żid il-paġni li noħloq fil-lista ta' osservazzjoni tiegħi",
-'tog-watchdefault' => "Żid il-paġni li nimmodifika fil-lista ta' osservazzjoni personali",
+'tog-watchcreations' => "Żid il-paġni li noħloq u l-fajls li ntella' fil-lista ta' osservazzjoni tiegħi",
+'tog-watchdefault' => "Żid il-paġni u l-fajls li nimmodifika fil-lista ta' osservazzjoni personali",
 'tog-watchmoves' => "Żid il-paġni li mmexxi fil-lista ta' osservazzjoni tiegħi",
 'tog-watchdeletion' => "Żid il-paġni li nħassar mal-lista ta' osservazzjoni tiegħi",
 'tog-minordefault' => 'Immarka awtomatikament kull modifika bħala waħda minuri',
@@ -297,7 +297,7 @@ $messages = array(
 'tog-nocache' => 'Iddiżattiva l-cache tal-paġni tal-browser',
 'tog-enotifwatchlistpages' => "Ibgħatli ittra-e kull meta sseħħ modifika fuq paġna li tinsab fil-lista ta' osservazzjoni tiegħi",
 'tog-enotifusertalkpages' => "Ibgħatli ittra-e kull meta l-paġna ta' diskussjoni tiegħi tiġi modifikata",
-'tog-enotifminoredits' => 'Ibgħatli wkoll ittra-e għall-modifiki minuri fuq paġni',
+'tog-enotifminoredits' => 'Ibgħatli wkoll ittra-e għall-modifiki minuri fuq paġni u fajls',
 'tog-enotifrevealaddr' => "Ikxef l-indirizz tal-posta elettronika tiegħi fil-messaġġi ta' avviż",
 'tog-shownumberswatching' => "Uri n-numru ta' utenti li qegħdin isegwu din il-paġna",
 'tog-oldsig' => 'Firma attwali:',
@@ -1204,6 +1204,7 @@ Nota li l-użu tal-links tan-navigazzjoni jagħmel reset tal-kolonna.",
 # Diffs
 'history-title' => 'Kronoloġija tal-modifiki ta\' "$1"',
 'difference-title' => 'Differenza bejn ir-reviżjonijiet ta\' "$1"',
+'difference-title-multipage' => 'Differenza bejn il-paġni "$1" u "$2"',
 'difference-multipage' => '(Differenzi bejn il-paġni)',
 'lineno' => 'Linja $1:',
 'compareselectedversions' => 'Qabbel il-verżjonijiet magħżula',
@@ -1301,6 +1302,7 @@ Innota però li l-werreja tal-kontenut ta' {{SITENAME}} f'dawn is-siti, jistgħu
 'prefs-beta' => 'Karatteristiċi tal-Beta',
 'prefs-datetime' => 'Data u ħin',
 'prefs-labs' => 'Karatteristiċi tal-laboratorji',
+'prefs-user-pages' => 'Paġni tal-utent',
 'prefs-personal' => 'Profil tal-utent',
 'prefs-rc' => 'Modifiki riċenti',
 'prefs-watchlist' => 'Osservazzjoni speċjali',
@@ -1476,6 +1478,7 @@ Hawnhekk hawn valur iġġenerat b'mod każwali li inti tista' tuża: $1",
 'right-writeapi' => 'Uża API sabiex tagħmel modifiki fil-wiki',
 'right-delete' => 'Ħassar paġni',
 'right-bigdelete' => "Ħassar paġni b'kronoloġija kbira",
+'right-deletelogentry' => "Ħassar u reġġa' lura daħliet speċifi tar-reġistru",
 'right-deleterevision' => 'Ħassar reviżjonijiet speċifiki tal-paġni',
 'right-deletedhistory' => 'Uri r-reviżjonijiet tal-kronoloġija li huma mħassra mingħajr it-test assoċjat.',
 'right-deletedtext' => 'Jara test imħassar u modifiki bejn reviżjonijiet imħassra',
@@ -1585,6 +1588,7 @@ Hawnhekk hawn valur iġġenerat b'mod każwali li inti tista' tuża: $1",
 'number_of_watching_users_pageview' => '[osservat minn {{PLURAL:$1|$1 utent|$1 utent}}]',
 'rc_categories' => 'Illimita għall-kategoriji (issepara b\' "|")',
 'rc_categories_any' => 'Kwalunkwe',
+'rc-change-size-new' => '$1 {{PLURAL:$1|byte|bytes}} wara l-modifika',
 'newsectionsummary' => '/* $1 */ sezzjoni ġdida',
 'rc-enhanced-expand' => 'Uri d-dettalji (hemm bżonn tal-JavaScript)',
 'rc-enhanced-hide' => 'Aħbi d-dettalji',
@@ -1743,6 +1747,10 @@ Jekk il-problema tkompli tippersisti, ikkuntatja lil xi [[Special:ListUsers/syso
 'upload-unknown-size' => 'Dimensjoni mhux magħrufa',
 'upload-http-error' => 'Qam żball HTTP: $1',
 
+# File backend
+'backend-fail-delete' => 'Il-fajl "$1" ma setax jiġi mħassar.',
+'backend-fail-alreadyexists' => 'Il-fajl "$1" diġà jeżisti.',
+
 # ZipDirectoryReader
 'zip-file-open-error' => 'Qam żball waqt il-ftuħ tal-fajl għall-kontrolli ZIP.',
 'zip-wrong-format' => 'Il-fajl speċifikat ma kienx fajl ZIP.',
@@ -1883,6 +1891,7 @@ Id-deskrizzjoni fuq il-[$2 paġna ta' deskrizzjoni tal-fajl] tinsab hawn taħt."
 ** Fajl duplikat",
 'filedelete-edit-reasonlist' => 'Immodifika r-raġunijiet għat-tħassir',
 'filedelete-maintenance' => "It-tħassir u r-restawr ta' fajls huwa diżattivat għall-mument minħabba xi manutenzjoni.",
+'filedelete-maintenance-title' => 'Il-fajl ma jistax jiġi mħassar',
 
 # MIME search
 'mimesearch' => 'Fittex fil-bażi għal tip MIME',
@@ -1931,7 +1940,7 @@ Daħħal: tip/sottotip, eż. <tt>image/jpeg</tt>.",
 'statistics-users-active-desc' => 'Utenti li wettqu azzjoni fl-aħħar {{PLURAL:$1|ġurnata|$1 ġurnata}}',
 'statistics-mostpopular' => 'Il-paġni l-aktar miżjura',
 
-'disambiguations' => "Paġni ta' diżambigwazzjoni",
+'disambiguations' => "Paġni li jorbtu lejn paġni ta' diżambigwazzjoni",
 'disambiguationspage' => 'Template:diżambig',
 'disambiguations-text' => "Il-Paġni li jinsabu f'din lista huma parti minn '''paġna ta' diżambigwazzjoni''' b'hekk għandhom jiġu relatati mas-suġġett preċiż minflok. <br />
 Paġna tiġi stimata paġna ta' diżambigwazzjoni dawk kollha li jagħmlu użu mit-template elenkat f'[[MediaWiki:Disambiguationspage]]",
@@ -2030,12 +2039,13 @@ Kun af li siti elettroniċi oħra jistgħu jorbtu b'ħolqa diretta lejn il-fajl,
 
 # Special:Log
 'specialloguserlabel' => 'Azzjoni effettwata minn:',
-'speciallogtitlelabel' => 'Titlu:',
+'speciallogtitlelabel' => 'Azzjoni effetwata fuq:',
 'log' => 'Reġistri',
 'all-logs-page' => 'Ir-reġistri pubbliċi kollha',
 'alllogstext' => "Preżentazzjoni unifikata tar-reġistri kollha ta' {{SITENAME}}. Tista' tqassar il-kriterji ta' tfittxija billi tagħżel it-tip ta' reġistru, l-isem tal-utent, jew il-paġna affetwata (it-tnejn tal-aħħar huma sensittivi għal kif jinkitbu l-karattri).",
 'logempty' => "Ir-reġistru m'għandu l-ebda element li jaqbel mat-tfittxija tiegħek.",
 'log-title-wildcard' => "Tfittxija ta' titli li jibdew b'dan it-test",
+'showhideselectedlogentries' => 'Uri/aħbi daħliet magħżula tar-reġistru',
 
 # Special:AllPages
 'allpages' => 'Il-paġni kollha',
@@ -2053,6 +2063,11 @@ Kun af li siti elettroniċi oħra jistgħu jorbtu b'ħolqa diretta lejn il-fajl,
 'allpagesprefix' => 'Uri l-paġni bil-prefiss:',
 'allpagesbadtitle' => "It-titlu indikat għal dil-paġna mhuwiex validu jew inkella fih xi prefiss interlingwa jew interwiki. Għaldaqstant, jista' ikun fih xi karratru(i) li ma jistgħux jintużaw fit-titli.",
 'allpages-bad-ns' => 'In-namespace "$1" ma jeżistix fuq {{SITENAME}}.',
+'allpages-hide-redirects' => 'Aħbi rindirizzi',
+
+# SpecialCachedPage
+'cachedspecial-viewing-cached-ttl' => "Qiegħed tara verżjoni ta' din il-paġna memorizzata fil-''cache'', li tista' tkun antika sa massimu ta' $1.",
+'cachedspecial-refresh-now' => 'Uri l-iktar riċenti.',
 
 # Special:Categories
 'categories' => 'Kategoriji',
@@ -2069,7 +2084,7 @@ Ara wkoll il-[[Special:WantedCategories|kategoriji rikjesti]].',
 'sp-deletedcontributions-contribs' => 'kontribuzzjonijiet',
 
 # Special:LinkSearch
-'linksearch' => 'Ħoloq esterni',
+'linksearch' => 'Fittex ħoloq esterni',
 'linksearch-pat' => "Mudell ta' tfittxija:",
 'linksearch-ns' => 'Spazju tal-isem:',
 'linksearch-ok' => 'Fittex',
@@ -2313,7 +2328,7 @@ Il-Preferenzi kurrenti ta' din il-paġni huma '''$1''':",
 'protect-othertime' => 'Ħin ieħor:',
 'protect-othertime-op' => 'ħin ieħor',
 'protect-existing-expiry' => 'Skadenza attwali: $2, $3',
-'protect-otherreason' => 'Raġunijiet oħra/addizjonali:',
+'protect-otherreason' => 'Raġunijiet oħra/addizzjonali:',
 'protect-otherreason-op' => 'Raġuni oħra',
 'protect-dropdown' => '*Raġunijiet komuni għall-protezzjoni
 ** Vandaliżmu eċċessiv
@@ -2472,7 +2487,7 @@ Indika r-raġuni speċifika għalfejn tixtieq tipproċedi bil-blokk (per eżempj
 'ipbcreateaccount' => "Impedixxi ħolqien ta' kontijiet oħrajn",
 'ipbemailban' => 'Impedixxi utenti milli jkunu jistgħu jibgħatu posta elettronika',
 'ipbenableautoblock' => 'Awtomatikament blokka l-aħħar indirizz tal-IP użat minn dan l-utent, u IP suċċessivi li jipprovaw jagħmlu modifiki',
-'ipbsubmit' => 'Imblokk lil dan l-utent',
+'ipbsubmit' => 'Imblokka lil dan l-utent',
 'ipbother' => 'Ħin ieħor:',
 'ipboptions' => 'sagħtejn:2 hours,ġurnata 1:1 day,3 ġranet:3 days,ġimgħa 1:1 week,ġimgħatejn:2 weeks,xahar 1:1 month,3 xhur:3 months,6 xhur:6 months,sena 1:1 year,infinita:infinite',
 'ipbotheroption' => 'ieħor',
@@ -2767,6 +2782,7 @@ Kull azzjonijiet ta' importazzjoni tal-transwiki jiġu reġistrati fil-[[Special
 'import-invalid-interwiki' => 'Ma jistax jiġi importat mill-wiki indikata.',
 'import-error-edit' => 'Il-paġna "$1" ma ġietx  impurtata minħabba li mintix awtorizzat li timmodifikaha.',
 'import-error-create' => 'Il-paġna "$1" ma ġietx  impurtata minħabba li mintix awtorizzat li toħloqha.',
+'import-error-invalid' => 'Il-paġna "$1" ma ġietx impurtata minħabba li isimha hi invalida.',
 
 # Import log
 'importlogpage' => 'Importazzjoni',
@@ -2776,6 +2792,14 @@ Kull azzjonijiet ta' importazzjoni tal-transwiki jiġu reġistrati fil-[[Special
 'import-logentry-interwiki' => 'Trasferixxejt minn wiki ieħor il-paġna $1',
 'import-logentry-interwiki-detail' => '{{PLURAL:$1|reviżjoni|$1 reviżjonijiet}} minn $2',
 
+# JavaScriptTest
+'javascripttest' => 'Testjar tal-JavaScript',
+'javascripttest-disabled' => 'Din il-funzjoni ma ġietx abilitata fuq din il-wiki.',
+'javascripttest-pagetext-noframework' => 'Din il-paġna hi riservata għall-eżekuzzjoni tat-testijiet tal-JavaScript.',
+'javascripttest-pagetext-frameworks' => 'Jekk jogħġbok agħżel wieħed mill-oqsma tal-ittestjar: $1',
+'javascripttest-pagetext-skins' => 'Agħżel aspett grafiku fuq liema tesegwixxi testijiet:',
+'javascripttest-qunit-intro' => 'Ara d-[$1 dokumentazzjoni dwar it-test] fuq mediawiki.org.',
+
 # Tooltip help for the actions
 'tooltip-pt-userpage' => 'Il-paġna tal-utent tiegħek',
 'tooltip-pt-anonuserpage' => "Il-Paġna tal-utent ta' dan l-indirizz tal-IP",
@@ -2836,6 +2860,8 @@ Kull azzjonijiet ta' importazzjoni tal-transwiki jiġu reġistrati fil-[[Special
 'tooltip-diff' => 'Uri liem modifiki għamilt fit-test.',
 'tooltip-compareselectedversions' => "Ara d-differenzi bejn iż-żewġ verżjonijiet magħżula ta' din il-paġna.",
 'tooltip-watch' => "Żid din il-paġna mal-lista ta' osservazzjoni tiegħek",
+'tooltip-watchlistedit-normal-submit' => 'Neħħi t-titli',
+'tooltip-watchlistedit-raw-submit' => "Aġġorna l-lista ta' osservazzjoni",
 'tooltip-recreate' => "Erġa' oħloq din il-paġna minkejja li kienet ġiet imħassra",
 'tooltip-upload' => "Ibda tella'",
 'tooltip-rollback' => '"Rollback" tannulla l-modifiki li saru mill-aħħar kontributur fuq din il-paġna, permezz ta\' sempliċi klikk',
@@ -2887,6 +2913,7 @@ Kull azzjonijiet ta' importazzjoni tal-transwiki jiġu reġistrati fil-[[Special
 'spambot_username' => 'Tindif tal-MedjaWiki mill-ispam',
 'spam_reverting' => "Erġa' lura għall-aħħar verżjoni li m'għandiex link għal $1",
 'spam_blanking' => 'Paġna svojtjata, kull verżjoni kellu link għal $1',
+'spam_deleting' => 'Paġna mħassra, ir-reviżjonijiet kollha kellhom ħoloq lejn $1',
 
 # Info page
 'pageinfo-title' => 'Informazzjoni għal "$1"',
@@ -3259,6 +3286,8 @@ Oħrajn jiġu moħbija kif inhu definit oriġinarjament.
 # Flash modes
 'exif-flash-fired-0' => "Il-''flash'' ma ħariġx",
 'exif-flash-fired-1' => "Il-''flash'' ħareġ",
+'exif-flash-function-1' => "''Flash'' diżattivata",
+'exif-flash-redeye-1' => 'modalità riduzzjoni tal-għajnejn ħomor',
 
 'exif-focalplaneresolutionunit-2' => 'pulzier',
 
@@ -3569,6 +3598,9 @@ Inti tista' wkoll tuża' l-[[Special:EditWatchlist|editur bl-interfaċċa standa
 'iranian-calendar-m9' => 'Azar',
 'iranian-calendar-m10' => 'Dey',
 
+# Signatures
+'signature' => '[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|diskussjoni]])',
+
 # Core parser functions
 'unknown_extension_tag' => 'Estensjoni tat-tag mhux magħrufa "$1"',
 'duplicate-defaultsort' => '\'\'\'Twissija:\'\'\' iċ-ċavetta tal-issortjar oriġinali "$2" tissostitwixxi dik preċedenti "$1".',
@@ -3601,6 +3633,9 @@ Flimkien ma' dan il-programm suppost kellek tirċievi [{{SERVER}}{{SCRIPTPATH}}/
 'version-software' => 'Softwer installat',
 'version-software-product' => 'Prodott',
 'version-software-version' => 'Verżjoni',
+'version-entrypoints' => "URL ta' aċċess",
+'version-entrypoints-header-entrypoint' => "Punt ta' dħul",
+'version-entrypoints-header-url' => 'URL',
 
 # Special:FilePath
 'filepath' => 'Post tal-fajl',
@@ -3705,12 +3740,18 @@ Stampi huwa mogħrija b'risoluzzjoni sħiħa, tipi tal-fajl oħrajn jibdew bil-p
 
 # New logging system
 'logentry-delete-delete' => '$1 ħassar il-paġna $3',
+'logentry-delete-restore' => "$1 reġġa' lura l-paġna $3",
+'logentry-delete-event' => "$1 biddel il-viżibilità ta' {{PLURAL:$5|azzjoni tar-reġistru|$5 azzjonijiet tar-reġistru}} ta' $3: $4",
 'revdelete-restricted' => "limiti applikati 'l amministraturi",
 'revdelete-unrestricted' => "neħħi l-limiti 'l amministraturi",
 'logentry-move-move' => '$1 mexxa l-paġna $3 għal $4',
 'logentry-move-move-noredirect' => '$1 mexxa l-paġna $3 għal $4 mingħajr ma ħalla rindirizz',
 'logentry-move-move_redir' => '$1 mexxa l-paġna $3 għal $4 fuq rindrizz',
 'logentry-move-move_redir-noredirect' => '$1 mexxa l-paġna $3 għal $4 fuq rindirizz mingħajr ma ħalla rindirizz',
+'logentry-newusers-newusers' => '$1 ħoloq kont tal-utent',
+'logentry-newusers-create' => '$1 ħoloq kont tal-utent',
+'logentry-newusers-create2' => '$1 ħoloq kont tal-utent $3',
+'logentry-newusers-autocreate' => 'Il-kont $1 ġie maħluq awtomatikament',
 'newuserlog-byemail' => "il-password intbagħtet permezz ta' posta elettronika",
 
 # Feedback
@@ -3726,4 +3767,26 @@ Stampi huwa mogħrija b'risoluzzjoni sħiħa, tipi tal-fajl oħrajn jibdew bil-p
 'feedback-bugcheck' => 'Tajjeb ħafna! Ivverifika li mhux diġà fost id-[$1 difetti magħrufa].',
 'feedback-bugnew' => 'Ikkontrollajt. Irrapporta d-difett',
 
+# API errors
+'api-error-fileexists-forbidden' => 'Fajl bl-isem "$1" diġà jeżisti, u ma jistax jiġi miktub fuqu.',
+'api-error-fileexists-shared-forbidden' => 'Fajl bl-isem "$1" diġà jeżisti fir-repożitorju maqsum u ma jistax jiġi miktub fuqu.',
+'api-error-file-too-large' => 'Il-fajl magħżul huwa wisq kbir.',
+'api-error-filename-tooshort' => 'L-isem tal-fajl huwa qasir wisq.',
+'api-error-filetype-banned' => "It-tip ta' fajl mhuwiex aċċettat.",
+'api-error-filetype-missing' => 'L-isem tal-fajl jonqsu l-estensjoni.',
+'api-error-illegal-filename' => 'L-isem tal-fajl mhuwiex permess.',
+'api-error-uploaddisabled' => "It-tlugħ ta' fajls mhuwiex attivat fuq din il-wiki.",
+'api-error-verification-error' => "Dan il-fajl jista' jkun imħassar, jew għandu l-estensjoni l-ħażina.",
+
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|sekonda|sekondi}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minuta|minuti}}',
+'duration-hours' => '$1 {{PLURAL:$1|siegħa|sigħat}}',
+'duration-days' => '$1 {{PLURAL:$1|jum|jiem}}',
+'duration-weeks' => '$1 {{PLURAL:$1|ġimgħa|ġimgħat}}',
+'duration-years' => '$1 {{PLURAL:$1|sena|snin}}',
+'duration-decades' => '$1 {{PLURAL:$1|deċennju|deċennji}}',
+'duration-centuries' => '$1 {{PLURAL:$1|seklu|sekli}}',
+'duration-millennia' => '$1 {{PLURAL:$1|millennju|millennji}}',
+
 );
index 1459d46..65b112e 100644 (file)
@@ -12,6 +12,7 @@
  * @author Hanzaw
  * @author Hintha
  * @author Lagoonaing
+ * @author Liangent
  * @author Lionslayer
  * @author Minnyoonthit
  * @author Myanmars
@@ -391,8 +392,8 @@ $1',
 'loginprompt' => '{{SITENAME}} သို့ လော့အင်ဝင်ရန် ကွတ်ကီးသုံးနိုင်ရန် ပြုလုပ်ပေးထားရမည်။',
 'userlogin' => 'Log in ဝင်ရန်/ အကောင့် လုပ်ရန်',
 'userloginnocreate' => 'Log in ဝင်ရန်',
-'logout' => 'Log out ထွက်ရန်',
-'userlogout' => 'Log out ထွက်ရန်',
+'logout' => 'ထွက်ရန်',
+'userlogout' => 'ထွက်ရန်',
 'notloggedin' => 'logged in ဝင်မထားပါ',
 'nologin' => 'အကောင့်မရှိဘဲ ဖြစ်နေပါသလား။ $1။',
 'nologinlink' => 'အကောင့်လုပ်ရန်',
index 4ff506d..b20735f 100644 (file)
@@ -1031,6 +1031,10 @@ U kunt [[Special:Search/{{PAGENAME}}|naar deze term zoeken]] in andere pagina\'s
 'noarticletext-nopermission' => 'Deze pagina bevat geen tekst.
 U kunt [[Special:Search/{{PAGENAME}}|naar deze term zoeken]] in andere pagina\'s of
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboeken doorzoeken]</span>.',
+'missing-revision' => 'De versie #$1 van de pagina "{{PAGENAME}} bestaat niet.
+
+Dit wordt meestal veroorzaakt door het volgen van een verouderde verwijzing naar een pagina die is verwijderd.
+Meer gegevens zijn mogelijk te vinden in het [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} verwijderlogboek].',
 'userpage-userdoesnotexist' => 'U bewerkt een gebruikerspagina van een gebruiker die niet bestaat (gebruiker "<nowiki>$1</nowiki>").
 Controleer of u deze pagina wel wilt aanmaken of bewerken.',
 'userpage-userdoesnotexist-view' => 'De gebruiker "$1" is niet geregistreerd.',
@@ -1164,6 +1168,7 @@ Deze parameters zijn weggelaten.',
 'expansion-depth-exceeded-warning' => 'De pagina bevat te veel sjablonen',
 'parser-unstrip-loop-warning' => 'Er is een "unstrip"-lus gedetecteerd',
 'parser-unstrip-recursion-limit' => 'De recursielimiet ($1) voor "unstrip" is overschreden',
+'converter-manual-rule-error' => 'Er is een fout gedetecteerd in een handmatig toegevoegde taalconversieregel.',
 
 # "Undo" feature
 'undo-success' => 'Deze bewerking kan ongedaan gemaakt worden.
@@ -1353,6 +1358,10 @@ Let op dat het gebruiken van de navigatieverwijzingen deze kolom opnieuw instelt
 'editundo' => 'ongedaan maken',
 'diff-multi' => '({{PLURAL:$1|Eén tussenliggende versie|$1 tussenliggende versies}} door {{PLURAL:$2|één gebruiker|$2 gebruikers}} {{PLURAL:$1|wordt|worden}} niet weergegeven)',
 'diff-multi-manyusers' => '($1 tussenliggende {{PLURAL:$1|versie|versies}} door meer dan $2 {{PLURAL:$2|gebruiker|gebruikers}}  worden niet weergegeven)',
+'difference-missing-revision' => '{{PLURAL:$2|Eén versie|$2 versies}} van deze verschillen ($1) {{PLURAL:$2|is|zijn}} niet aangetroffen.
+
+Dit wordt meestal veroorzaakt door het volgen van een verouderde verwijzing verschillen voor een pagina die is verwijderd.
+Meer gegevens zijn mogelijk te vinden in het [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} verwijderlogboek].',
 
 # Search results
 'searchresults' => 'Zoekresultaten',
index fda9002..20f02db 100644 (file)
@@ -1194,7 +1194,7 @@ Pass på at den nye sida også har innhald frå den innfletta sida.',
 'difference-multipage' => '(Skilnad mellom sider)',
 'lineno' => 'Line $1:',
 'compareselectedversions' => 'Samanlikn valde versjonar',
-'showhideselectedversions' => 'Syn/skjul valde versjonar',
+'showhideselectedversions' => 'Vis/løyn valde versjonar',
 'editundo' => 'angre',
 'diff-multi' => '({{PLURAL:$1|Éin mellomversjon|$1 mellomversjonar}} frå {{PLURAL:$2|éin brukar|$2 brukarar}} er ikkje {{PLURAL:$1|vist|viste}})',
 'diff-multi-manyusers' => '({{PLURAL:$1|Ein mellomversjon|$1 mellomversjonar}} av meir enn $2 {{PLURAL:$2|brukar|brukarar}}  er ikkje {{PLURAL:$1|vist|viste}})',
@@ -1349,7 +1349,7 @@ Her er det framlegg til eit tal som kan nyttast, tilfelleleg henta fram: $1',
 'prefs-custom-js' => 'Eigendefinert JavaScript',
 'prefs-common-css-js' => 'Delt CSS/JavaScript for alle draktene:',
 'prefs-reset-intro' => 'Du kan nytta denne sida til å tilbakestilla innstillingane dine til standardinnstillingane.
-Dette kan ikke tilbakestillast.',
+Dette kan ikkje tilbakestillast.',
 'prefs-emailconfirm-label' => 'Stadfesting av e-post:',
 'prefs-textboxsize' => 'Storleiken til redigeringsvindauga',
 'youremail' => 'E-post:',
index 86e004b..67400b9 100644 (file)
@@ -263,7 +263,7 @@ $messages = array(
 'tog-hidepatrolled' => 'ନଗଦ ବଦଳରେ ଥିବା ଜଗାହୋଇଥିବା ବଦଳ ସବୁକୁ ଲୁଚାଇବେ',
 'tog-newpageshidepatrolled' => 'ନୂଆ ପୃଷ୍ଠାତାଲିକାରୁ ଜଗାହୋଇଥିବା ବଦଳସବୁକୁ ଲୁଚାଇବେ',
 'tog-extendwatchlist' => 'କେବଳ ନଗଦ ନୁହେଁ, ସବୁଯାକ ବଦଳକୁ ଦେଖାଇବା ନିମନ୍ତେ ଦେଖଣାତାଲିକାକୁ ବଢ଼ାଇବେ',
-'tog-usenewrc' => 'à¬\89ନà­\8dନତ à¬¨à¬\97ଦ à¬¬à¬¦à¬³ à¬¬à­\8dà­\9fବହାର à¬\95ରନà­\8dତà­\81 (ଜାଭାସ୍କ୍ରିପ୍ଟ ଲୋଡ଼ା)',
+'tog-usenewrc' => 'ନà¬\97ଦ à¬¬à¬¦à¬³à¬°à­\87 à¬ªà­\83ଷà­\8dଠା à¬\85ନà­\81ଯାà­\9fà­\80 à¬\97à­\8bଷà­\8dଠà­\80 à¬¬à¬¦à¬³ à¬\8fବà¬\82 à¬¦à­\87à¬\96ଣା (ଜାଭାସ୍କ୍ରିପ୍ଟ ଲୋଡ଼ା)',
 'tog-numberheadings' => 'ଆପେଆପେ-ସଂଖ୍ୟାର ନାମଗୁଡ଼ିକ',
 'tog-showtoolbar' => 'ସମ୍ପାଦନା ଟୁଲବାର ଦେଖାଇବେ (ଜାଭାସ୍କ୍ରିପ୍ଟ ସଚଳ କରିବେ)',
 'tog-editondblclick' => 'ଦୁଇଥର କ୍ଲିକରେ ପୃଷ୍ଠା ବଦଳାଇବେ (ଜାଭାସ୍କ୍ରିପ୍ଟ ଲୋଡ଼ା)',
@@ -271,17 +271,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'ବିଭାଗ ନାମରେ ଡାହାଣ କ୍ଲିକ କରି ବିଭାଗ ସମ୍ପାଦନାକୁ ସଚଳ କରିବେ (ଜାଭାସ୍କ୍ରିପ୍ଟ ଲୋଡ଼ା)',
 'tog-showtoc' => 'ସୂଚୀପତ୍ର ଦେଖାଇବେ (୩ରୁ ଅଧିକ ମୁଖ୍ୟ ନାମ ଥିଲେ)',
 'tog-rememberpassword' => 'ଏହି ବ୍ରାଉଜରରେ (ସବୁଠୁ ଅଧିକ ହେଲେ $1 {{PLURAL:$1|day|ଦିନ}}) ପାଇଁ ମୋ ଲଗଇନ ମନେ ରଖିଥିବେ',
-'tog-watchcreations' => 'ମà­\8b à¬¤à¬¿à¬\86ରି à¬ªà­\83ଷà­\8dଠାସବà­\81à¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ା à¬­à¬¿à¬¤à¬°à­\87 à¬°à¬\96ିବà­\87',
-'tog-watchdefault' => 'ମà­\8b à¬¦à­\87à¬\87 à¬¸à¬®à­\8dପାଦିତ à¬ªà­\83ଷà­\8dଠାସବà­\81à¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ା à¬­à¬¿à¬¤à¬°à­\87 à¬°à¬\96ିବà­\87',
-'tog-watchmoves' => 'ମà­\8b à¬¦à­\87à¬\87 à¬\98à­\81à¬\9eà­\8dà¬\9aାଯାà¬\87ଥିବା à¬ªà­\83ଷà­\8dଠାସବà­\81à¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ା à¬­à¬¿à¬¤à¬°à­\87 à¬°à¬\96ିବà­\87',
-'tog-watchdeletion' => 'ମà­\8b à¬¦à­\87à¬\87 à¬²à¬¿à¬­à¬¾à¬¯à¬¾à¬\87ଥିବା à¬ªà­\83ଷà­\8dଠାସବà­\81à¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ା à¬­à¬¿à¬¤à¬°à­\87 à¬°à¬\96ିବà­\87',
+'tog-watchcreations' => 'ମà­\8b à¬¤à¬¿à¬\86ରି à¬ªà­\83ଷà­\8dଠାସବà­\81à¬\95à­\81 à¬\8fବà¬\82 à¬®à­\8b à¬\85ପଲà­\8bଡà¬\97à­\81ଡିà¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ାରà­\87 à¬¯à­\8bଡନà­\8dତà­\81',
+'tog-watchdefault' => 'ମà­\81à¬\81 à¬¬à¬¦à¬³à­\87à¬\87ଥିବା à¬ªà­\83ଷà­\8dଠା à¬\8fବà¬\82 à¬«à¬¾à¬\87ଲà¬\97à­\81ଡିà¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ାରà­\87 à¬¯à­\8bଡନà­\8dତà­\81',
+'tog-watchmoves' => 'ମà­\81à¬\81 à¬\98à­\81à¬\9eà­\8dà¬\9aାà¬\87ଥିବା à¬ªà­\83ଷà­\8dଠା à¬\8fବà¬\82 à¬«à¬¾à¬\87ଲà¬\97à­\81ଡିà¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ାରà­\87 à¬¯à­\8bଡନà­\8dତà­\81',
+'tog-watchdeletion' => 'ମà­\81à¬\81 à¬²à¬¿à¬­à¬¾à¬\87ଥିବା à¬ªà­\83ଷà­\8dଠା à¬\8fବà¬\82 à¬«à¬¾à¬\87ଲà¬\97à­\81ଡିà¬\95à­\81 à¬®à­\8bର à¬¦à­\87à¬\96ଣାତାଲିà¬\95ାରà­\87 à¬¯à­\8bଡନà­\8dତà­\81',
 'tog-minordefault' => 'ସବୁଯାକ ସମ୍ପାଦନାକୁ ଛାଏଁ ଟିକେ ବଦଳ ଭାବରେ ସୂଚିତ କରିବେ',
 'tog-previewontop' => 'ଏଡ଼ିଟ ବାକ୍ସ ଆଗରୁ ଦେଖଣା ଦେଖାଇବେ',
 'tog-previewonfirst' => 'ପ୍ରଥମ ବଦଳର ଦେଖଣା ଦେଖାଇବେ',
 'tog-nocache' => 'ବ୍ରାଉଜର ପୃଷ୍ଠା ସଂରକ୍ଷଣକୁ ଅଚଳ କରିବେ',
 'tog-enotifwatchlistpages' => 'ମୋ ଦେଖଣାତାଲିକାରେ ଥିବା ପୃଷ୍ଠା ବା ଫାଇଲରେ କିଛି ବଦଳ ହେଲେ ମୋତେ ଇ-ମେଲ କରିବେ',
 'tog-enotifusertalkpages' => 'ମୋର ଆଲୋଚନା ପୃଷ୍ଠାରେ କିଛି ବଦଳ ହେଲେ ମୋତେ ଇ-ମେଲ କରିବେ',
-'tog-enotifminoredits' => 'ପୃଷ୍ଠାରେ ଛୋଟ ଛୋଟ ବଦଳ ହେଲେ ବି ମୋତେ ଇ-ମେଲ କରିବେ',
+'tog-enotifminoredits' => 'ପà­\83ଷà­\8dଠାରà­\87 à¬\8fବà¬\82 à¬«à¬¾à¬\87ଲà¬\97à­\81ଡିà¬\95ରà­\87 à¬\9bà­\8bà¬\9f à¬\9bà­\8bà¬\9f à¬¬à¬¦à¬³ à¬¹à­\87ଲà­\87 à¬¬à¬¿ à¬®à­\8bତà­\87 à¬\87-ମà­\87ଲ à¬\95ରିବà­\87',
 'tog-enotifrevealaddr' => 'ସୂଚନା ଇ-ମେଲ ରେ ମୋର ଇ-ମେଲ ଠିକଣା ଦେଖାଇବେ',
 'tog-shownumberswatching' => 'ଦେଖୁଥିବା ବ୍ୟବହାରକାରୀଙ୍କ ସଂଖ୍ୟା ଦେଖାଇବେ',
 'tog-oldsig' => 'ଏବେ ଥିବା ନାମ:',
@@ -519,6 +519,8 @@ $1',
 'youhavenewmessages' => 'ଆପଣଙ୍କର $1 ($2).',
 'newmessageslink' => 'ନୂଆ ମେସେଜ',
 'newmessagesdifflink' => 'ଶେଷ ବଦଳ',
+'youhavenewmessagesmanyusers' => 'ଆପଣଙ୍କର ବହୁତ ବ୍ୟବହାରକାରୀ($2)ମାନଙ୍କଠାରୁ $1 ଅଛି ।',
+'newmessagesdifflinkplural' => 'ଶେଷ{{PLURAL:$1|change|changes}}',
 'youhavenewmessagesmulti' => '$1 ତାରିଖରେ ନୂଆ ଚିଠିଟିଏ ଆସିଛି',
 'editsection' => 'ସମ୍ପାଦନା',
 'editold' => 'ଏହାକୁ ବଦଳାନ୍ତୁ',
@@ -611,11 +613,13 @@ $1',
 'badarticleerror' => 'ଏହି ପୃଷ୍ଠାରେ ଏହି କାମଟି ହୋଇପାରିବ ନାହିଁ ।',
 'cannotdelete' => '"$1" ପୃଷ୍ଠା ବା ଫାଇଲଟି ଲିଭାଯାଇପାରିବ ନାହିଁ । ଏହା ଆଗରୁ କାହା ଦେଇ ବୋଧେ ଲିଭାଇ ଦିଆଯାଇଛି ।',
 'cannotdelete-title' => '"$1" ପୃଷ୍ଠାଟି ଲିଭଯାଇପାରିବ ନାହିଁ',
+'delete-hook-aborted' => 'ସମ୍ପାଦନା ଏକ ହୁକ (hook) ଦେଇ ବାରଣ କରାଗଲା ।
+ଏହା କିଛି ବି କାରଣ ଦେଇନାହିଁ ।',
 'badtitle' => 'ଖରାପ ନାଆଁ',
 'badtitletext' => 'ଆପଣ ଅନୁରୋଧ କରିଥିବା ପୃଷ୍ଠାଟି ଭୁଲ, ଖାଲି ଅଛି ବା ବାକି ଭାଷା ସାଙ୍ଗରେ ଭୁଲରେ ଯୋଡ଼ା ଯାଇଛି ବା ଭୁଲ ଇଣ୍ଟର ଉଇକି ନାମ ଦିଆଯାଇଛି ।
 ଏଥିରେ ଥିବା ଗୋଟିଏ ବା ଦୁଇଟି ଅକ୍ଷର ଶିରୋନାମା ଭାବରେ ବ୍ୟବହାର କରାଯାଇ ପାରିବ ନାହିଁ ।',
-'perfcached' => 'ତଳଲିà¬\96ିତ à¬¤à¬¥à­\8dà­\9fà¬\9fି à¬\86à¬\97ରà­\81 à¬°à¬¹à¬¿à¬¥à¬¿à¬¬à¬¾ à¬¤à¬¥à­\8dà­\9f, à¬¤à­\87ଣà­\81 à¬¨à¬\97ଦ à¬¹à­\8bà¬\87ନପାରà­\87 à¥¤ A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.',
-'perfcachedts' => 'ତଳଲିଖିତ ତଥ୍ୟ ଆଗରୁ ଥିବା ତଥ୍ୟ ଓ  $1ରେ ଶେଷଥର ଅପଡେଟ ହୋଇଥିଲା । A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.',
+'perfcached' => 'ତଳଲିà¬\96ିତ à¬¤à¬¥à­\8dà­\9fà¬\97à­\81ଡିà¬\95à­\81 à¬\85ସà­\8dଥାà­\9fà­\80 à¬­à¬¾à¬¬à­\87 à¬°à¬\96ାà¬\97ଲା à¬\8fବà¬\82 à¬\8fହା à¬\85ପଡà­\87à¬\9f à¬¨ à¬¹à­\8bà¬\87ପାରà­\87 à¥¤ à¬\85ତିବà­\87ଶିରà­\87 {{PLURAL:$1|ଫଳ|$1ଫଳà¬\97à­\81ଡିà¬\95 }} à¬\85ସà­\8dଥାà­\9fà­\80 à¬°à­\82ପà­\87 à¬°à¬¹à¬¿ à¬ªà¬¾à¬°à¬¿à¬¬ à¥¤',
+'perfcachedts' => 'ତଳଲିଖିତ ତଥ୍ୟଗୁଡିକୁ ଅସ୍ଥାୟୀ ଭାବେ ରଖାଗଲା ଏବଂ  $1ରେ ଶେଷଥର ଅପଡେଟ ହୋଇଥିଲା । ଅତିବେଶିରେ {{PLURAL:$1|ଫଳ|$1ଫଳଗୁଡିକ }} ଅସ୍ଥାୟୀ ରୂପେ ରହି ପାରିବ ।',
 'querypage-no-updates' => 'ଏହି ପୃଷ୍ଠାଟି ପାଇଁ ଅପଡେଟସବୁ ଏବେ ଅଚଳ କରାଯାଇଅଛି ।
 ଏଠାରେ ଥିବା ତଥ୍ୟ ସବୁ ଏବେ ସତେଜ ହୋଇପାରିବ ନାହିଁ ।',
 'wrong_wfQuery_params' => 'wfQuery() ପାଇଁ ଭୁଲ ପାରାମିଟର<br />
@@ -642,6 +646,8 @@ $2',
 'ns-specialprotected' => 'ବିଶେଷ ପୃଷ୍ଠାସବୁକୁ ବଦଳାଯାଇପାରିବ ନାହିଁ ।',
 'titleprotected' => 'ଏହି ନାମଟି [[User:$1|$1]]ଙ୍କ ଦେଇ ନୂଆ ତିଆରିହେବାରୁ କିଳାଯାଇଅଛି ।
 ଏହାର କାରଣ ହେଲା "\'\'$2\'\'" ।',
+'exception-nologin' => 'ଲଗ‌‌ ଇନ କରିନାହାନ୍ତି',
+'exception-nologin-text' => 'ଏହା କରିବାକୁ ହେଲେ ଆପଣଙ୍କୁ ଏହି ଉଇକିରେ ଲଗଇନ କରିବାକୁ ପଡିବ ।',
 
 # Virus scanner
 'virus-badscanner' => "ମନ୍ଦ ସଂରଚନା: ଅଜଣା ଭାଇରସ ସ୍କାନର: ''$1''",
@@ -661,6 +667,7 @@ $2',
 'remembermypassword' => 'ଏହି ବ୍ରାଉଜରରେ (ସବୁଠୁ ଅଧିକ ହେଲେ $1 {{PLURAL:$1|day|ଦିନ}}) ପାଇଁ ମୋ ଲଗଇନ ମନେ ରଖିଥିବେ',
 'securelogin-stick-https' => 'ଲଗ ଇନ କଲାପରେ HTTPS ସହ ଯୋଡ଼ି ହୋଇ ରହନ୍ତୁ',
 'yourdomainname' => 'ଆପଣଙ୍କ ଡୋମେନ:',
+'password-change-forbidden' => 'ଆପଣ ଏହି ଉଇକିରେ ପାସୱାର୍ଡ ବଦଳାଇ ପାରିବେ ନାହିଁ ।',
 'externaldberror' => 'ବୋଧ ହୁଏ ଚିହ୍ନଟ ଡାଟାବେସ ଭୁଲଟିଏ ହୋଇଥିଲା ବା ଆପଣଙ୍କୁ ନିଜର ବାହାର ଖାତା ଅପଡେଟ କରିବା ନିମନ୍ତେ ଅନୁମତି ମିଳିନାହିଁ ।',
 'login' => 'ଲଗଇନ',
 'nav-login-createaccount' => 'ଲଗ ଇନ /ନୂଆ ଖାତା ଖୋଲନ୍ତୁ',
@@ -739,6 +746,7 @@ continue using your old password.',
 'invalidemailaddress' => 'ଏହି ଇ-ମେଲ ଠିକଣାଟି ସଠିକ ସଜାଣିରେ ନଥିବାରୁ ଏହାକୁ ଗ୍ରହଣ କରାଯାଇପାରିବ ନାହିଁ ।
 ଦୟାକରି ଏକ ସଚଳ ଓ ଠିକ ସଜାଣିରେ ଥିବା ଇ-ମେଲ ଠିକଣା ଦିଅନ୍ତୁ ।',
 'cannotchangeemail' => 'ଖାତାରେ ଥିବା ଇମେଲ ଏହି ଉଇକିରେ ବଦଳାଯାଇପାରିବ ନାହିଁ ।',
+'emaildisabled' => 'ଏହି ସାଇଟ ଇ-ମେଲ ପଠାଇ ପାରିବ ନାହିଁ ।',
 'accountcreated' => 'ଖାତାଟି ଖୋଲାହୋଇଗଲା',
 'accountcreatedtext' => '$1 ପାଇଁ ନୂଆ ଖାତାଟିଏ ତିଆରି ହୋଇଗଲା ।',
 'createaccount-title' => '{{SITENAME}} ପାଇଁ ଖାତା ଖୋଲା',
@@ -946,6 +954,7 @@ $1 ଦ୍ଵାରା ପ୍ରତିରୋଧ କରାଯାଇଛି
 'note' => "'''ଟୀକା:'''",
 'previewnote' => "'''ଜାଣିରଖନ୍ତୁ ଯେ, ଏହା କେବଳ ଏକ ଦେଖଣା ।'''
 ଆପଣ କରିଥିବା ବଦଳସବୁ ଏଯାଏଁ ସାଇତା ଯାଇନାହିଁ!",
+'continue-editing' => 'ବଦଳାଇବା ଜାରି ରଖନ୍ତୁ',
 'previewconflict' => 'ଉପରେ ଦିଶୁଥିବା ଏହି ଦେଖଣାକୁ ସାଇତିଲେ ଏହା ଏକାପରି ଦେଖାଯିବ ।',
 'session_fail_preview' => "'''କ୍ଷମା କରିବେ! ଅବଧି ତଥ୍ୟ ନଷ୍ଟ ହୋଇଯାଇଥିବାରୁ ଆମେ ଆପଣଙ୍କ ବଦଳସବୁକୁ ଗ୍ରହଣ କରିପାରିଲୁ ନାହିଁ ।'''
 ଦୟାକରି ଆଉଥରେ ଚେଷ୍ଟା କରନ୍ତୁ ।
@@ -961,6 +970,7 @@ $1 ଦ୍ଵାରା ପ୍ରତିରୋଧ କରାଯାଇଛି
 ଆପଣ ଏକ ୱେବ-ରେ ଥିବା ଅଜଣା ପ୍ରକ୍ସି ସାଇଟ କରି  ବ୍ୟବହାର କରୁଥିଲେ ଏପରି ହୋଇଥାଏ ।",
 'edit_form_incomplete' => "'''ସମ୍ପାଦନାର କେତେକ ଭାଗ ସର୍ଭର ଠେଇଁ ପହଞ୍ଚିଲା ନାହିଁ; ଭଲକରି ପରଖିନିଅନ୍ତୁ ଯେ ନିଜ ସମ୍ପାଦନା ସବୁ ଅକ୍ଷତ କି ନାହିଁ ଓ ଆଉଥରେ ଚେଷ୍ଟା କରନ୍ତୁ ।'''",
 'editing' => '$1 କୁ ବଦଳାଉଛି',
+'creating' => '$1କୁ ତିଆରି କରୁଛି',
 'editingsection' => '$1 (ଭାଗ)କୁ ବଦଳାଇବେ',
 'editingcomment' => '$1 (ନୂଆ ଭାଗ)କୁ ବଦଳାଉଛୁ',
 'editconflict' => 'ବଦଳାଇବା ଦ୍ଵନ୍ଦ: $1',
@@ -984,7 +994,7 @@ $1 ଦ୍ଵାରା ପ୍ରତିରୋଧ କରାଯାଇଛି
 ଯଦି ଆପଣ ନିଜର ଲେଖା ନିର୍ଦୟ ଭାବେ ସମ୍ପାଦିତ ହେଉ ବୋଲି ଚାହୁଁନାହାନ୍ତି ବା ବଣ୍ଟନ କରାଯାଉ ବୋଲି ଚାହୁଁ ନାହାନ୍ତି ତେବେ ତାହା ଏଠାରେ ଦିଅନ୍ତୁ ନାହିଁ ।<br />
 ଆପଣ ଆମପକ୍ଷେ ମଧ୍ୟ ପ୍ରତିଜ୍ଞା କରୁଛନ୍ତି ଯେ ଏହା ଆପଣ ନିଜେ ଲେଖିଛନ୍ତି, କିମ୍ବା ଏକ ପବ୍ଲିକ ଡୋମେନରୁ ବା ମାଗଣା ଓ ଖୋଲା ଲାଇସେନ୍ସ ଥିବା ସାଇଟରୁ ନକଲ କରି ଆଣିଛନ୍ତି । (ଦୟାକରି ସବିଶେଷ ପାଇଁ $1 ଦେଖନ୍ତୁ) ।
 '''ଅନୁମତି ବିନା ସତ୍ଵାଧିକାର ଥିବା କାମ ଏଠାରେ ଦିଅନ୍ତୁ ନାହିଁ !'''",
-'longpageerror' => "'''ଭୁଲ: ଆପଣ ଦେଇଥିବା ଲେଖାଟି $1 କିଲୋବାଇଟ ଲମ୍ବା, ଯାହାକି ସବୁଠାରୁ ଅଧିକ $2 ଠାରୁ ବି ଅଧିକ ।'''
+'longpageerror' => "'''ଭୁଲ: ଆପଣ ଦେଇଥିବା ଲେଖାଟି {{PLURAL:$1|କିଲୋବାଇଟ|$1 କିଲୋବାଇଟ}} ଲମ୍ବା, ଯାହାକି ସବୁଠାରୁ ଅଧିକ {{PLURAL:$2|କିଲୋବାଇଟ|$2 କିଲୋବାଇଟ}} ଠାରୁ ବି ଅଧିକ ।'''
 ଏହା ସାଇତାଯାଇପାରିବ ନାହିଁ ।",
 'readonlywarning' => "'''ସୂଚନା: ଏହି ଡାଟାବେସଟି ରକ୍ଷଣାବେକ୍ଷଣା ପାଇଁ କିଳାଯାଇଛି । ତେଣୁ ଆପଣ ଆପଣା ସମ୍ପାଦନା ଏବେ ସାଇତି ପାରିବେ ନାହିଁ ।'''
 ଆପଣ ଲେଖାସବୁ ଏକ ଟେକ୍ସଟ ଫାଇଲରେ ନକଲ କରି ପେଷ୍ଟ କରି ଆଗକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ସାଇତି ପାରିବେ ।
@@ -1027,6 +1037,7 @@ $1 ଦ୍ଵାରା ପ୍ରତିରୋଧ କରାଯାଇଛି
 'edit-no-change' => 'ଆପଣଙ୍କ ସମ୍ପାଦନାକୁ ଅଣଦେଖା କରାଗଲା, କାରଣ ଲେଖାରେ କିଛି ବି ବଦଳ କରାଯାଇନଥିଲା ।',
 'edit-already-exists' => 'ନୂଆ ପୃଷ୍ଠାଟିଏ ତିଆରି କରିପାରିଲୁଁ ନାହିଁ ।
 ଏହା ଅଗରୁ ଅଛି ।',
+'defaultmessagetext' => 'ଡିଫଲ୍ଟ ମେସେଜ ଲେଖାଗୁଡିକ',
 
 # Parser/template warnings
 'expensive-parserfunction-warning' => "'''ଚେତାବନୀ:''' ଏହି ପୃଷ୍ଠାରେ ଅନେକ ଗୁଡ଼ିଏ ମୂଲ୍ୟବାନ ପାର୍ସର ଫଙ୍କସନ କଲ ଅଛି ।
@@ -1188,8 +1199,8 @@ $1",
 
 # Suppression log
 'suppressionlog' => 'ଦବାଇବା ଲଗ',
-'suppressionlogtext' => 'ଲିଭାଯାଇଥିବା ଓ ଅଟକାଯାଇଥିବା ବସ୍ତୁର ଏକ ତାଲିକା ତଳେ ଦିଆଯାଇଛି ଯେଉଁଥିରେ ପରିଛାମାନଙ୍କଠାରୁ ଲୁଚାଯାଇଥିବା ବସ୍ତୁ ମଧ୍ୟ ଅଛି ।
-ଏବେ କରାଯାଇଥିବା ବାସନ୍ଦ ଓ ବାରଣ ପାଇଁ [[Special:BlockList|IP ଅଟକ ତାଲିକା]] ଦେଖନ୍ତୁ ।',
+'suppressionlogtext' => 'ଲିଭାଯାଇଥିବା ଓ ଅଟକାଯାଇଥିବା, ଏହା ସହ ପରିଛାମାନଙ୍କଠାରୁ ଲୁଚାଯାଇଥିବା ଲେଖାଗୁଡ଼ିକର ଏକ ତାଲିକା ତଳେ ଦିଆଯାଇଛି ।
+ଏବେ କରାଯାଇଥିବା ବାସନ୍ଦ ଓ ବାରଣ ପାଇଁ [[Special:BlockList|block list]] ଦେଖନ୍ତୁ ।',
 
 # History merging
 'mergehistory' => 'ପୃଷ୍ଠାର ଇତିହାସ ସବୁ ଯୋଡ଼ିଦେବେ',
@@ -1222,7 +1233,7 @@ $1",
 'mergelogpagetext' => 'ତଳେ ସବୁଠାରୁ ନଗଦ ଯୋଡ଼ାଯାଇଥିବା ପୃଷ୍ଠାର ଇତିହାସ ଆଉ ଗୋଟିଏ ସହ ଦିଆଯାଇଅଛି ।',
 
 # Diffs
-'history-title' => '"$1" à¬ªà¬¾à¬\87à¬\81 à¬¸à¬\99à­\8dà¬\95ଳନ ଇତିହାସ',
+'history-title' => '"$1" à¬° à¬ªà­\81ନରାବà­\83ତି ଇତିହାସ',
 'difference-multipage' => '(ପୃଷ୍ଠା ଭିତରେ ଥିବା ତଫାତ)‌',
 'lineno' => '$1 କ ଧାଡ଼ି:',
 'compareselectedversions' => 'ବଛାହୋଇଥିବା ସଙ୍କଳନ ଗୁଡ଼ିକୁ ତଉଲିବେ',
@@ -1784,7 +1795,7 @@ $1',
 'backend-fail-writetemp' => 'ଅସ୍ଥାୟୀ ଫାଇଲ ତିଆରି କରିପାରିଲୁ ନାହିଁ ।',
 'backend-fail-closetemp' => 'ଅସ୍ଥାୟୀ ଫାଇଲ ବନ୍ଦ କରିହେଲା ନାହିଁ ।',
 'backend-fail-read' => '$1 ଫାଇଲଟି ପଢ଼ିପାରିଲୁ ନାହିଁ ।',
-'backend-fail-create' => '$1 à¬«à¬¾à¬\87ଲà¬\9fି à¬¤à¬¿à¬\86ରି à¬\95ରିପାରିଲà­\81 ନାହିଁ ।',
+'backend-fail-create' => '$1 à¬«à¬¾à¬\87ଲରà­\87 à¬\95ିà¬\9bି à¬²à­\87à¬\96ି à¬¹à­\87ଲା ନାହିଁ ।',
 
 # Lock manager
 'lockmanager-notlocked' => 'କିଳାଯାଇଥିବା "$1"କୁ ଖୋଲିପାରିଲୁ ନାହିଁ; ଏହା ପ୍ରକୃତରେ କିଳାଯାଇନାହିଁ ।',
@@ -3865,6 +3876,12 @@ MediaWiki ଉପଯୋଗୀ ହେବା ଲକ୍ଷରେ ବଣ୍ଟାଯ
 'api-error-uploaddisabled' => 'ଉଇକିରେ ଅପଲୋଡ଼ କରିବା ଅଚଳ କରାଯାଇଅଛି ।',
 'api-error-verification-error' => 'ଏହି ଫାଇଲଟି ବୋଧ ହୁଏ ନଷ୍ଟ ହୋଇଯାଇଅଛି କିମ୍ବା ଭୁଲ ଏକ୍ସଟେନସନ ଦିଆଯାଇଅଛି ।',
 
+# Durations
+'duration-years' => '$1 {{PLURAL:$1|year|years}}',
+'duration-decades' => '$1 {{PLURAL:$1|decade|decades}',
+'duration-centuries' => '$1 {{PLURAL:$1|century|centuries}}',
+'duration-millennia' => '$1 {{PLURAL:$1|millennium|millennia}}',
+
 # Unknown messages
 'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|ଏକ ଅନୁମୋଦିତ ଫାଇଲ ପ୍ରକାର ନୁହେଁ|ମାନ ଅନୁମୋଦିତ ଫାଇଲ ପ୍ରକାର ନୁହଁନ୍ତି}} । ଅନୁମୋଦିତ {{PLURAL:$3|ଫାଇଲ ପ୍ରକାର ହେଲା|ଫାଇଲ ପ୍ରକାର ହେଲା}} $2 ।',
 );
index faa96d7..095860d 100644 (file)
@@ -64,30 +64,63 @@ $messages = array(
 'tog-hidepatrolled' => 'ਮੌਨਜੁਦਾ ਬਦਲਾਬ ਮੈ ਸੈ ਸਹੀਤਕ ਬਦਲਾਬ ਕੌ ਛੁਪਾ ਕਰ ਰਖੇ.',
 'tog-newpageshidepatrolled' => 'ਨਵੀ ਸੁਚੀ ਮੈ ਸੈ ਗਸ਼ਤ ਪਰਚੇ ਕੌ ਛੁਪਾਏ.',
 'tog-extendwatchlist' => 'ਸਾਰੀ ਨਵੀ ਤਬਦੀਲੀਆ ਹੀ ਨਹੀ ,ਪੂਰਾਣੀ ਤਬਦੀਲੀਆ ਨੂੰ ਵੀ ਨਵੀ ਸੂਚੀ ਵਿਚ ਵਧਾ ਕੈ ਸ਼ਾਮੀਲ ਕਰੌ.',
-'tog-usenewrc' => 'ਸà©\81ਦਾਰ à¨\95à©\80ਤà©\87 à¨¹à©\8cà¨\82à¨\82à¨\9eà©\87 à¨°à©\82à¨\9aà©\80 à¨¦à©\80  à¨µà¨°à¨¤à©\8c à¨\95ਰà©\8c (à¨\9cਰà©\82ਰਤ à¨¹à©\88 à¨\9cਾਵਾ à¨¸à¨\95à©\8dਰਿਪà©\8dà¨\9f à¨\95à©\80)',
+'tog-usenewrc' => 'ਤਾà¨\9c਼ਾ à¨¤à¨¬à¨¦à©\80ਲà©\80à¨\86à¨\82 à¨\85ਤà©\87 à¨¨à¨¿à¨\97ਰਾਨà©\80 à¨²à¨¿à¨¸à¨\9f à¨µà¨¿à¨\9a à¨¤à¨¬à¨¦à©\80ਲà©\80à¨\86à¨\82 à¨¸à¨«à¨¼à©\87 à¨®à©\81ਤਾਬà¨\95 (à¨\9cਾਵਾ à¨¸à¨\95à©\8dਰਿਪà¨\9f à¨²à©\8bà©\9cà©\80à¨\82ਦà©\80 à¨¹à©\88)',
 'tog-numberheadings' => 'ਆਟੋ-ਨੰਬਰ ਹੈਡਿੰਗ',
 'tog-showtoolbar' => 'ਐਡਿਟ ਟੂਲਬਾਰ ਵੇਖੋ (JavaScript)',
+'tog-editondblclick' => 'ਦੋਹਰੇ ਕਲਿੱਕ ਨਾਲ਼ ਸਫ਼ਾ ਸੋਧੋ (ਜਾਵਾ ਸਕ੍ਰਿਪਟ ਲੋੜੀਂਦੀ ਹੈ)',
+'tog-editsection' => '[ਸੋਧੋ] ਲਿੰਕਾਂ ਜ਼ਰੀਏ ਸੈਕਸ਼ਨ ਸੋਧ ਯੋਗ ਕਰੋ',
+'tog-editsectiononrightclick' => 'ਸੈਕਸ਼ਨ ਸਿਰਲੇਖਾਂ ਤੇ ਸੱਜੀ ਕਲਿੱਕ ਦੁਆਰਾ ਸੋਧ ਯੋਗ ਕਰੋ (ਜਾਵਾ ਸਕ੍ਰਿਪਟ ਲੋੜੀਂਦੀ ਹੈ)',
 'tog-showtoc' => 'ਟੇਬਲ ਆਫ਼ ਕੰਨਟੈੱਟ ਵੇਖਾਓ (for pages with more than 3 headings)',
 'tog-rememberpassword' => 'ਇਸ ਬਰਾਊਜ਼ਰ ਉੱਤੇ ਮੇਰਾ ਲਾਗਇਨ ਯਾਦ ਰੱਖੋ ($1 {{PLURAL:$1|ਦਿਨ|ਦਿਨਾਂ}} ਲਈ ਵੱਧ ਤੋਂ ਵੱਧ)',
-'tog-watchcreations' => 'ਮà©\87ਰà©\87 à¨µà¨²à©\8bà¨\82 à¨¬à¨£à¨¾à¨\8f à¨\97à¨\8f à¨¨à¨µà©\87à¨\82 à¨¸à¨«à¨¼à©\87 à¨®à©\87ਰà©\80 à¨µà¨¾à¨\9a-ਲਿਸਟ ਵਿੱਚ ਪਾਓ',
-'tog-watchdefault' => 'à¨\9cà©\8b à¨¸à¨«à¨¼à©\87 à¨®à©\88à¨\82 à¨¸à©\8bਧਦਾ à¨¹à¨¾à¨\82, à¨\93ਹ à¨ªà©\87à¨\9c à¨®à©\87ਰà©\80 à¨µà¨¾à¨\9a-ਲਿਸਟ ਵਿੱਚ ਪਾਓ',
-'tog-watchmoves' => 'ਮà©\87ਰà©\87 à¨µà¨²à©\8bà¨\82 à¨­à©\87à¨\9cà©\87 à¨\95ਿਤà©\87 à¨¸à¨«à¨¼à©\87 à¨¨à©\82à©° à¨®à©\87ਰà©\80 à¨µà¨¾à¨\9a-ਲਿਸਟ ਵਿੱਚ ਪਾਓ',
+'tog-watchcreations' => 'ਮà©\87ਰà©\87 à¨µà¨²à©\8bà¨\82 à¨¬à¨£à¨¾à¨\8f à¨\97à¨\8f à¨¨à¨µà©\87à¨\82 à¨¸à¨«à¨¼à©\87 à¨\85ਤà©\87 à¨\85ੱਪਲà©\8bਡ à¨\95à©\80ਤà©\80à¨\86à¨\82 à¨«à¨¼à¨¾à¨\88ਲਾà¨\82 à¨®à©\87ਰà©\80 à¨¨à¨¿à¨\97ਰਾਨà©\80-ਲਿਸਟ ਵਿੱਚ ਪਾਓ',
+'tog-watchdefault' => 'ਮà©\87ਰà©\87 à¨µà©±à¨²à©\8bà¨\82 à¨¸à©\8bਧà©\87 à¨¸à¨«à¨¼à©\87 à¨\85ਤà©\87 à¨«à¨¼à¨¾à¨\88ਲਾà¨\82 à¨®à©\87ਰà©\80 à¨¨à¨¿à¨\97ਰਾਨà©\80-ਲਿਸਟ ਵਿੱਚ ਪਾਓ',
+'tog-watchmoves' => 'ਮà©\87ਰà©\87 à¨µà©±à¨²à©\8bà¨\82 à¨¬à¨¦à¨²à©\87 à¨¸à¨¿à¨°à¨²à©\87à¨\96ਾà¨\82 à¨µà¨¾à¨²à¨¼à©\87 à¨¸à¨«à¨¼à©\87 à¨\85ਤà©\87 à¨«à¨¼à¨¾à¨\88ਲਾà¨\82 à¨®à©\87ਰà©\80 à¨¨à¨¿à¨\97ਰਾਨà©\80-ਲਿਸਟ ਵਿੱਚ ਪਾਓ',
 'tog-watchdeletion' => 'ਮੇਰੇ ਵਲੋਂ ਹਟਾਏ ਗਏ ਸਫ਼ੇ ਮੇਰੀ ਵਾਚ-ਲਿਸਟ ਵਿੱਚ ਪਾਓ',
+'tog-minordefault' => 'ਸਾਰੀਆਂ ਸੋਧਾਂ ਤੇ ਮੂਲ ਰੂਪ ਵਿਚ ਛੋਟੀਆਂ ਹੋਣ ਦਾ ਨਿਸ਼ਾਨ ਲਾਓ',
 'tog-previewontop' => 'ਐਡਿਟ ਬਕਸੇ ਤੋਂ ਪਹਿਲਾਂ ਝਲਕ ਵੇਖਾਓ',
 'tog-previewonfirst' => 'ਪਹਿਲੇ ਐਡਿਟ ਉੱਤੇ ਝਲਕ ਵੇਖਾਓ',
 'tog-nocache' => 'ਬਰਾਊਜ਼ਰ ਸਫ਼ਾ ਕੈਸ਼ ਕਰਨਾ ਬੰਦ ਕਰੋ',
-'tog-enotifwatchlistpages' => 'ਜਦੋਂ ਮੇਰੀ ਵਾਚ-ਲਿਸਟ ਵਿੱਚੋਂ ਸਫ਼ਾ ਬਦਲਿਆ ਜਾਵੇ ਤਾਂ ਮੈਨੂੰ ਈਮੇਲ ਭੇਜੋ',
+'tog-enotifwatchlistpages' => 'ਜਦੋਂ ਮੇਰੀ ਵਾਚ-ਲਿਸਟ ਵਿਚ ਦਰਜ ਕੋਈ ਸਫ਼ਾ ਬਦਲਿਆ ਜਾਵੇ ਯਾ ਮਿਸਲ ਬਦਲੀ ਜਾਵੇ ਤਾਂ ਮੈਨੂੰ ਈਮੇਲ ਭੇਜੋ',
+'tog-enotifusertalkpages' => 'ਜਦੋਂ ਮੇਰਾ ਗੱਲ-ਬਾਤ ਸਫ਼ਾ ਬਦਲਿਆ ਜਾਵੇ ਤਾਂ ਮੈਨੂੰ ਈ-ਮੇਲ ਭੇਜੋ',
+'tog-enotifminoredits' => 'ਸਫ਼ਿਆਂ ਅਤੇ ਫ਼ਾਈਲਾਂ ਦੀਆਂ ਛੋਟੀਆਂ ਤਬਦੀਲੀਆਂ ਲਈ ਵੀ ਮੈਨੂੰ ਈ-ਮੇਲ ਭੇਜੋ',
+'tog-enotifrevealaddr' => 'ਜਾਣੂ ਕਰਵਾਉਣ ਵਾਲ਼ੀਆਂ ਈ-ਮੇਲਾਂ ਵਿਚ ਮੇਰਾ ਈ-ਮੇਲ ਪਤਾ ਜ਼ਾਹਰ ਕਰੋ',
+'tog-shownumberswatching' => 'ਨਜ਼ਰ ਰੱਖ ਰਹੇ ਮੈਂਬਰਾਂ ਦੀ ਗਿਣਤੀ ਵਖਾਓ',
 'tog-oldsig' => 'ਮੌਜੂਦਾ ਦਸਤਖਤ:',
+'tog-fancysig' => 'ਦਸਤਖ਼ਤ ਨੂੰ ਵਿਕੀਲਿਖਤ ਦੇ ਤੌਰ ਤੇ ਵਰਤੋ (ਬਿਨਾਂ ਆਟੋਮੈਟਿਕ ਲਿੰਕ)',
+'tog-externaleditor' => 'ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਬਾਹਰੀ ਸੋਧਕ ਵਰਤੋ (ਸਿਰਫ਼ ਮਾਹਿਰਾਂ ਲਈ ਹੈ, ਤੁਹਾਡੇ ਕੰਪਿਊਟਰ ਤੇ ਖ਼ਾਸ ਸੈਟਿੰਗਾਂ ਲੋੜੀਂਦੀਆਂ ਹਨ। [//
+www.mediawiki.org/wiki/
+Manual:External_editors More
+information.])',
+'tog-externaldiff' => 'ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਬਾਹਰੀ ਫ਼ਰਕ ਵਰਤੋ (ਸਿਰਫ਼
+ਮਾਹਿਰਾਂ ਲਈ ਹੈ, ਤੁਹਾਡੇ ਕੰਪਿਊਟਰ ਤੇ ਖ਼ਾਸ
+ਸੈਟਿੰਗਾਂ ਲੋੜੀਂਦੀਆਂ ਹਨ। [//
+www.mediawiki.org/wiki/
+Manual:External_editors More
+information.])',
+'tog-showjumplinks' => '"ਇਸ ਤੇ ਜਾਓ" ਦੇ ਲਿੰਕ ਦਿਖਾਣਾ ਸਮਰੱਥ ਕਰੋ',
+'tog-uselivepreview' => 'ਸਿੱਧੀ ਝਲਕ ਵਰਤੋ (ਜਾਵਾਸਕ੍ਰਿਪਟ ਲੋੜੀਂਦੀ ਹੈ) (ਤਜਰਬੇਕਾਰੀ)',
+'tog-forceeditsummary' => 'ਜਦੋਂ ਮੈਂ ਖ਼ਾਲੀ ਸੋਧ ਸਾਰ ਦਾਖ਼ਲ ਕਰਾਂ ਤਾਂ ਮੈਨੂੰ ਖ਼ਬਰਦਾਰ ਕਰੋ',
 'tog-watchlisthideown' => 'ਮੇਰੀ ਵਾਚ-ਲਿਸਟ ਵਿੱਚੋਂ ਮੇਰੀਆਂ ਸੋਧਾਂ ਹਟਾਓ',
 'tog-watchlisthidebots' => 'ਮੇਰੀ ਵਾਚ-ਲਿਸਟ ਵਿੱਚੋਂ ਰੋਬਾਟ ਦਿਆਂ ਸੋਧਾਂ ਹਟਾਓ',
 'tog-watchlisthideminor' => 'ਛੋਟੇ ਸੋਧ ਵਾਚ-ਲਿਸਟ ਤੋਂ ਓਹਲੇ ਰੱਖੋ',
+'tog-watchlisthideliu' => 'ਨਿਗਰਾਨੀ-ਲਿਸਟ ਵਿਚੋਂ ਲਾਗ ਇਨ ਕੀਤੇ ਮੈਂਬਰਾਂ ਦੀਆਂ ਸੋਧਾਂ ਲੁਕਾਓ',
+'tog-watchlisthideanons' => 'ਨਿਗਰਾਨੀ-ਲਿਸਟ ਵਿਚ ਗੁਮਨਾਮ ਮੈਂਬਰਾਂ ਦੀਆਂ ਕੀਤੀਆਂ ਸੋਧਾਂ ਲੁਕਾਓ',
+'tog-watchlisthidepatrolled' => 'ਵੇਖੀਆਂ ਜਾ ਚੁੱਕੀਆਂ ਸੋਧਾਂ ਨਿਗਰਾਨੀ-ਲਿਸਟ ਵਿਚੋਂ ਲੁਕਾਓ',
+'tog-ccmeonemails' => 'ਜੋ ਈ-ਮੇਲਾਂ ਮੈਂ ਦੂਜੇ ਮੈਂਬਰਾਂ ਨੂੰ ਭੇਜਦਾ ਹਾਂ ਓਹਨਾਂ ਦੀਆਂ ਨਕਲਾਂ ਮੈਨੂੰ ਭੇਜੋ',
+'tog-diffonly' => 'ਫ਼ਰਕਾਂ ਤੋਂ ਹੇਠ ਸਫ਼ੇ ਦੀ ਸਮੱਗਰੀ ਨਾ ਵਖਾਓ',
+'tog-showhiddencats' => 'ਲੁਕੀਆਂ ਸ਼੍ਰੇਣੀਆਂ ਵਖਾਓ',
+'tog-norollbackdiff' => '"ਵਾਪਸ ਮੋੜੌ"ਅਮਲ ਵਿਚ ਲਿਆਣ ਬਾਦ ਫ਼ਰਕ ਨਾ ਦਿਖਾਓ',
 
 'underline-always' => 'ਹਮੇਸ਼ਾਂ',
 'underline-never' => 'ਕਦੇ ਨਹੀਂ',
 'underline-default' => 'ਬਰਾਊਜ਼ਰ ਡਿਫਾਲਟ',
 
 # Font style option in Special:Preferences
+'editfont-style' => 'ਸੰਪਾਦਨ ਖੇਤਰ ਦੇ ਅੱਖਰਾਂ ਦੀ ਫ਼ੌਂਟ ਰੀਤੀ',
 'editfont-default' => 'ਬਰਾਊਜ਼ਰ ਡਿਫਾਲਟ',
+'editfont-monospace' => 'ਇੱਕੋ ਜਿਹੀ ਖ਼ਾਲੀ ਥਾਂ ਵਾਲ਼ਾ ਅੱਖਰ',
+'editfont-sansserif' => 'Sans-serif ਨਾਂ ਦਾ ਫ਼ੌਂਟ',
+'editfont-serif' => 'ਨੋਕਦਾਰ ਅੱਖਰ',
 
 # Dates
 'sunday' => 'ਐਤਵਾਰ',
@@ -148,11 +181,17 @@ $messages = array(
 'category-media-header' => 'ਕੈਟਾਗਰੀ "$1" ਵਿੱਚ ਮੀਡੀਆ',
 'category-empty' => "''ਇਸ ਕੈਟਾਗਰੀ ਵਿੱਚ ਇਸ ਵੇਲ਼ੇ ਕੋਈ ਵੀ ਲੇਖ ਜਾਂ ਮੀਡੀਆ ਨਹੀਂ ਹੈ।''",
 'hidden-categories' => '{{PLURAL:$1|ਲੁਕੀਵੀਂ ਸ਼੍ਰੇਣੀ|ਲੁਕਵੀਂਆਂ ਸ਼੍ਰੇਣੀਆਂ}}',
+'hidden-category-category' => 'ਲੁਕੀਆਂ ਸ਼੍ਰੇਣੀਆਂ',
 'category-subcat-count' => '{{ਕੁੱਲ $2 ਸ਼੍ਰੇਣੀਆਂ ਵਿਚੋਂ, PLURAL:$2|ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ ਸਿਰਫ਼ ਹੇਠ ਲਿਖੀ ਸ਼੍ਰੇਣੀ ਹੈ| ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ {{PLURAL:$1|ਉਪ ਸ਼੍ਰੇਣੀ ਹੈ|$1 ਉਪ-ਸ਼੍ਰੇਣੀਆਂ ਹਨ}}}}',
+'category-subcat-count-limited' => 'ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ {{PLURAL:$1|ਉਪ-ਸ਼੍ਰੇਣੀ ਹੈ।|$1 ਉਪ-ਸ਼੍ਰੇਣੀਆਂ ਹਨ।}}',
 'category-article-count' => '{{PLURAL:$2|ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ ਸਿਰਫ਼ ਇਹ ਸਫ਼ਾ ਹੈ|ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ, ਕੁੱਲ $2 ਵਿਚੋਂ, ਇਹ {{PLURAL:$1|ਸਫ਼ਾ ਹੈ|$1 ਸਫ਼ੇ}} ਹਨ}}',
+'category-article-count-limited' => 'ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ ਇਹ {{PLURAL:$1|ਸਫ਼ਾ ਹੈ|$1 ਸਫ਼ੇ ਹਨ।}}',
 'category-file-count' => '{{PLURAL:$2|ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ ਸਿਰਫ਼ ਇਹ ਫ਼ਾਈਲ ਹੈ।| ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ {{PLURAL:$1|ਫ਼ਾਈਲ ਹੈ|$1 ਫ਼ਾਈਲਾਂ ਹਨ।}}}}',
+'category-file-count-limited' => 'ਇਸ ਸ਼੍ਰੇਣੀ ਵਿਚ ਇਹ {{PLURAL:$1|ਫ਼ਾਈਲ ਹੈ|$1 ਫ਼ਾਈਲਾਂ ਹਨ।}}',
 'listingcontinuesabbrev' => 'ਜਾਰੀ',
+'index-category' => 'ਤਤਕਰੇ ਵਾਲ਼ੇ ਸਫ਼ੇ',
 'noindex-category' => 'ਕ੍ਰਮਸੂਚੀ ਰਹਿਤ ਸਫ਼ੇ',
+'broken-file-category' => 'ਟੁੱਟੇ ਹੋਏ ਫ਼ਾਈਲ ਜੋੜਾਂ ਵਾਲ਼ੇ ਸਫ਼ੇ',
 
 'about' => 'ਇਸ ਬਾਰੇ',
 'article' => 'ਸਮੱਗਰੀ ਪੇਜ',
@@ -183,6 +222,7 @@ $messages = array(
 'vector-action-protect' => 'ਸੁਰੱਖਿਅਤ ਬਣਾਓ',
 'vector-action-undelete' => 'ਹਟਾਉਣਾ ਵਾਪਸ',
 'vector-action-unprotect' => 'ਸੁਰੱਖਿਆ ਬਦਲੋ',
+'vector-simplesearch-preference' => 'ਵਾਧੂ ਖੋਜ ਸਲਾਹਾਂ ਯੋਗ ਕਰੋ (ਸਿਰਫ਼ ਵਿਕਟਰ ਸਕਿੰਨ ਵਿਚ)',
 'vector-view-create' => 'ਬਣਾਓ',
 'vector-view-edit' => 'ਸੋਧ',
 'vector-view-history' => 'ਅਤੀਤ ਵੇਖੋ',
@@ -214,6 +254,7 @@ $messages = array(
 'delete' => 'ਮਿਟਾਓ',
 'deletethispage' => 'ਇਹ ਪੇਜ ਹਟਾਓ',
 'undelete_short' => 'ਅਣ-ਹਟਾਓ {{PLURAL:$1|one edit|$1 edits}}',
+'viewdeleted_short' => '{{PLURAL:$1|ਇਕ ਮਿਟਾਈ ਸੋਧ|$1 ਮਿਟਾਈਆਂ ਸੋਧਾਂ}} ਵੇਖੋ',
 'protect' => 'ਸੁਰੱਖਿਆ',
 'protect_change' => 'ਤਬਦੀਲੀ',
 'protectthispage' => 'ਇਹ ਪੇਜ ਸੁਰੱਖਿਅਤ ਕਰੋ',
@@ -246,6 +287,12 @@ $messages = array(
 'jumpto' => 'ਇਸ ’ਤੇ ਜਾਓ:',
 'jumptonavigation' => 'ਰਹਿਨੁਮਾਈ',
 'jumptosearch' => 'ਖੋਜੋ',
+'view-pool-error' => 'ਆਫ਼ਸੋਸ, ਸਰਵਰ ਇਸ ਵੇਲ਼ੇ ਓਵਰਲੋਡ ਹੈ।
+ਬਹੁਤ ਸਾਰੇ ਮੈਂਬਰ ਇਸ ਸਫ਼ੇ ਨੂੰ ਵੇਖਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੇ ਹਨ।
+ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਥੋੜੀ ਉਡੀਕ ਕਰੋ।
+$1',
+'pool-timeout' => 'ਲੌਕ ਲਈ ਉਡੀਕ ਦਾ ਵਕਤ ਖ਼ਤਮ ਹੋ ਗਿਆ ਹੈ',
+'pool-queuefull' => 'ਪੂਲ ਕਤਾਰ ਪੂਰੀ ਲੱਦੀ ਹੈ',
 'pool-errorunknown' => 'ਅਣਜਾਣ ਗਲਤੀ',
 
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
@@ -270,12 +317,21 @@ $messages = array(
 
 'badaccess' => 'ਅਧਿਕਾਰ ਗਲਤੀ',
 'badaccess-group0' => 'ਤੁਹਾਨੂੰ ਉਹ ਐਕਸ਼ਨ ਕਰਨ ਦੀ ਮਨਜ਼ੂਰੀ ਨਹੀਂ, ਜਿਸ ਦੀ ਤੁਸੀਂ ਮੰਗ ਕੀਤੀ ਹੈ।',
+'badaccess-groups' => 'ਜੋ ਕੰਮ ਤੁਸੀਂ ਕਰਨਾ ਚਾਹਿਆ ਹੈ ਓਹ {{PLURAL:$2|ਇਸ ਗਰੁੱਪ|ਇਹਨਾਂ ਗਰੁੱਪਾਂ}} ਦੇ ਮੈਂਬਰ ਹੀ ਕਰ ਸਕਦੇ ਹਨ: $1',
+
+'versionrequired' => 'ਮੀਡੀਆਵਿਕੀ ਦੇ $1 ਵਰਜਨ ਦੀ ਲੋੜ ਹੈ',
+'versionrequiredtext' => 'ਇਸ ਸਫ਼ੇ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਮੀਡੀਆਵਿਕੀ ਦੇ $1 ਵਰਜਨ ਦੀ ਲੋੜ ਹੈ।
+ਵੇਖੋ [[Special:Version|ਵਰਜਨ ਸਫ਼ੇ]]।',
 
 'ok' => 'ਠੀਕ ਹੈ',
 'retrievedfrom' => '"$1" ਤੋਂ ਲਿਆ',
 'youhavenewmessages' => 'ਤੁਹਾਡੇ ਲਈ $1। ($2)',
 'newmessageslink' => 'ਨਵੇਂ ਸੁਨੇਹੇ',
 'newmessagesdifflink' => 'ਆਖ਼ਰੀ ਤਬਦੀਲੀ',
+'youhavenewmessagesfromusers' => '{{PLURAL:$3|ਇਕ  ਮੈਂਬਰ|$3 ਮੈਂਬਰਾਂ}} ਵੱਲੋਂ ਤੁਹਾਨੂੰ $1 ($2)',
+'youhavenewmessagesmanyusers' => 'ਕਈ ਮੈਂਬਰਾਂ ਵੱਲੋਂ ਤੁਹਾਨੂੰ $1 ($2)।',
+'newmessageslinkplural' => '{{PLURAL:$1|ਇੱਕ ਨਵਾਂ ਸੁਨੇਹਾ|ਨਵੇਂ ਸੁਨੇਹੇ}}',
+'newmessagesdifflinkplural' => 'ਆਖ਼ਰੀ {{PLURAL:$1|ਤਬਦੀਲੀ|ਤਬਦੀਲੀਆਂ}}',
 'youhavenewmessagesmulti' => 'ਤੁਹਾਨੂੰ ਨਵੇਂ ਸੁਨੇਹੇ $1 ਉੱਤੇ ਹਨ',
 'editsection' => 'ਸੋਧ',
 'editold' => 'ਸੋਧੋ',
@@ -290,7 +346,11 @@ $messages = array(
 'collapsible-expand' => 'ਫੈਲਾਓ',
 'thisisdeleted' => 'ਵੇਖੋ ਜਾਂ $1 ਰੀਸਟੋਰ?',
 'viewdeleted' => '$1 ਵੇਖਣਾ?',
+'restorelink' => '{{PLURAL:$1|ਇਕ ਮਿਟਾਈ ਹੋਈ ਸੋਧ|$1
+ਮਿਟਾਈਆਂ ਹੋਈਆਂ ਸੋਧਾਂ}}',
 'feedlinks' => 'ਫੀਡ:',
+'feed-invalid' => 'ਸਬਸਕ੍ਰਿਪਸ਼ਨ ਫ਼ੀਡ ਦੀ ਅਵੈਧ ਕਿਸਮ',
+'feed-unavailable' => 'ਸੰਸਥਾਵਾਂ  ਸਮੱਗਰੀ ਦਾ ਆਧੁਨਕੀਕਰਣ ਉਪਲਬਧ ਨਹੀਂ',
 'site-rss-feed' => '$1 RSS ਫੀਡ',
 'site-atom-feed' => '$1 ਐਟਮ ਫੀਡ',
 'page-rss-feed' => '"$1" RSS ਫੀਡ',
@@ -313,6 +373,9 @@ $messages = array(
 
 # Main script and global functions
 'nosuchaction' => 'ਕੋਈ ਇੰਝ ਦਾ ਐਕਸ਼ਨ ਨਹੀਂ',
+'nosuchactiontext' => 'URL ਦੁਆਰਾ ਦੱਸਿਆ ਕੰਮ ਗ਼ਲਤ ਹੈ।
+ਸ਼ਾਇਦ ਤੁਸੀਂ URL ਸਹੀ ਨਹੀਂ ਲਿਖਿਆ ਜਾਂ ਕਿਸੇ ਗ਼ਲਤ ਲਿੰਕ ਤੇ ਆਏ ਹੋ।
+ਇਹ ਵੀ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਹ {{SITENAME}} ਦੁਆਰੇ ਵਰਤੇ ਜਾਂਦੇ ਸਾਫ਼ਟਵੇਅਰ ਵਿਚਲੀ ਗ਼ਲਤੀ ਵੱਲ ਇਸ਼ਾਰਾ ਹੋਵੇ।',
 'nosuchspecialpage' => 'ਕੋਈ ਇੰਝ ਦਾ ਖਾਸ ਪੇਜ ਨਹੀਂ',
 'nospecialpagetext' => '<strong>ਤੁਸੀਂ ਇੱਕ ਅਵੈਧ ਖਾਸ ਪੇਜ ਦੀ ਮੰਗ ਕੀਤੀ ਹੈ।</strong>
 
@@ -321,12 +384,43 @@ A list of valid special pages can be found at [[Special:SpecialPages]].',
 # General errors
 'error' => 'ਗਲਤੀ',
 'databaseerror' => 'ਡਾਟਾਬੇਸ ਗਲਤੀ',
+'dberrortext' => 'ਡੈਟਾਬੇਸ ਪੁ੍ਛ ਗਿੱਛ ਵਿਚ ਹਿਦਾਇਤਾਂ ਦੀ ਤਰੁੱਟੀ ਮਿਲੀ ਹੈ।
+ਹੋ ਸਕਦਾ ਹੈ ਇਹ ਤਰੁ੍ੱਟੀ ਸਾਫ਼ਟਵੇਅਰ ਦੀ ਹੋਵੇ।
+ਇਸ ਗਣਿਤਫ਼ਲ "<tt>$2</tt>" ਵਿਚੌਂ ਪਿਛਲੀ ਡੈਟਬਾਸ ਪੁੱਛ ਗਿੱਛ ਸੀ: <blockquote><tt>$1</tt></blockquote.
+ਡੈਟਾਬੇਸ ਨੇ ਇਹ ਤਰੁੱਟੀ "<tt>$3: $4</tt>"ਜਵਾਬ ਵਿਚ ਦਿੱਤੀ।',
+'dberrortextcl' => 'ਡੈਟਾਬੇਸ ਪੁ੍ਛ ਗਿੱਛ ਵਿਚ ਹਿਦਾਇਤਾਂ ਦੀ ਤਰੁੱਟੀ ਮਿਲੀ ਹੈ।
+ਹੋ ਸਕਦਾ ਹੈ ਇਹ ਤਰੁੱਟੀ ਸਾਫ਼ਟਵੇਅਰ ਦੀ ਹੋਵੇ।
+ਇਸ ਗਣਿਤਫ਼ਲ "$2" ਵਿਚੌਂ ਪਿਛਲੀ ਡੈਟਬਾਸ ਪੁੱਛ ਗਿੱਛ ਸੀ: "$1".
+ਡੈਟਾਬੇਸ ਨੇ ਇਹ ਤਰੁੱਟੀ "$1"ਜਵਾਬ ਵਿਚ ਦਿੱਤੀ।',
+'laggedslavemode' => "'''ਖ਼ਬਰਦਾਰ:''' ਹੋ ਸਕਦਾ ਹੈ ਸਫ਼ੇ ਵਿਚ ਤਾਜ਼ਾ ਤਬਦੀਲੀਆਂ ਸ਼ਾਮਲ ਨਾ ਹੋਣ।",
 'readonly' => 'ਡਾਟਾਬੇਸ ਲਾਕ ਹੈ',
+'enterlockreason' => 'ਤਾਲਾ-ਬੰਦੀ ਲਈ ਕਾਰਨ ਦਾਖ਼ਲ ਕਰੋ, ਨਾਲ਼ ਹੀ ਤਾਲਾ-ਬੰਦੀ ਦੇ ਰਿਲੀਜ਼ ਹੋਣ ਦਾ ਅੰਦਾਜ਼ਨ ਵਕਤ',
+'readonlytext' => 'ਡੈਟਾਬੇਸ ਨੂੰ ਇਸ ਵੇਲ਼ੇ ਤਾਲਾ ਲੱਗਾ ਹੋਇਆ ਹੈ, ਸ਼ਾਇਦ ਆਮ ਰੱਖ-ਰਖਾਵ ਲਈ, ਇਸਤੋਂ ਬਾਅਦ ਇਹ ਆਮ ਵਾਂਗ ਉਪਲੱਬਧ ਹੋਵੇਗਾ।
+ਜਿਸ ਪ੍ਰਬੰਧਕ ਨੇ ਇਸਨੂੰ ਤਾਲਾ ਲਾਇਆ ਹੈ ਉਸਦਾ ਕਹਿਣਾ ਹੈ ਕਿ: $1',
 'missing-article' => "ਡਾਟਾਬੇਸ ਨੂੰ ''$1'' $2 ਨਾਮ ਦਾ ਕੋਈ ਸਫ਼ਾ ਨਹੀਂ ਮਿਲਿਆ।
 ਆਮ ਤੌਰ ਤੇ ਮਿਟਾਏ ਜਾ ਚੁੱਕੇ ਸਫ਼ੇ ਦੀ ਅਤੀਤ ਕੜੀ ਦੀ ਵਰਤੋਂ ਕਰਨ ਨਾਲ਼ ਇੰਝ ਹੁੰਦਾ ਹੈ।
 ਜੇ ਇਹ ਗੱਲ ਨਹੀਂ ਤਾਂ ਹੋ ਸਕਦਾ ਹੈ ਤੁਹਾਨੂੰ ਸਾਫ਼ਟਵੇਅਰ ਵਿਚ ਖ਼ਾਮੀ ਮਿਲ ਗਈ ਹੈ। ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਸਫ਼ੇ ਦੇ ਪਤੇ ਸਮੇਤ [[Special:ListUsers/sysop|administrator]] ਨੂੰ ਇਤਲਾਹ ਦਿਓ।",
+'missingarticle-rev' => '(ਬਦਲਾਅ#: $1)',
+'missingarticle-diff' => '(ਫ਼ਰਕ: $1, $2)',
+'readonly_lag' => 'ਜਦੌਂ ਤਕ ਅਧੀਨ ਡੇਟਾਬੇਸ ਸਰਵਰ ਸੁਤੰਤਰ ਡੈਟਾਬੇਸ ਸਰਵਰ ਦੀ ਪਕੜ ਵਿਚ ਨਹੀਂ ਆ ਜਾਂਦੇ ਡੈਟਾਬੇਸ ਸਵੈ ਜਕੜਿਆ ਗਿਆ ਹੈ।',
 'internalerror' => 'ਅੰਦਰੂਨੀ ਗਲਤੀ',
 'internalerror_info' => 'ਅੰਦਰੂਨੀ ਗਲਤੀ: $1',
+'fileappenderrorread' => 'ਅੰਤਕਾ ਜੋੜਨ ਲਗਿਆਂ "$1"ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ।',
+'fileappenderror' => "''$1'' ''$2'' ਨਾਲ਼ ਜੋੜਿਆ ਨਹੀ ਜਾ ਸਕਦਾ",
+'filecopyerror' => "'''$1''' ਫ਼ਾਈਲ '''$2''' ਵਿਚ ਨਕਲ ਨਹੀਂ ਹੋ ਸਕੀ।",
+'filerenameerror' => "ਫ਼ਾਈਲ ''$1'' ਦਾ ਨਾਮ ਬਦਲ ਕੇ ''$2'' ਨਹੀਂ ਰੱਖਿਆ ਜਾ ਸਕਿਆ।",
+'filedeleteerror' => "ਫ਼ਾਈਲ ''$1'' ਮਿਟਾਈ ਨਹੀਂ ਜਾ ਸਕੀ।",
+'directorycreateerror' => "''$1'' ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ।",
+'filenotfound' => "ਫ਼ਾਈਲ ''$1'' ਲੱਭੀ ਨਹੀਂ ਜਾ ਸਕੀ।",
+'fileexistserror' => 'ਮਿਸਲ "$1" ਤੇ ਲਿਖ ਨਹੀਂ ਸਕਦੇ: ਮਿਸਲ ਹੌਂਦ ਵਿਚ ਹੈ।',
+'unexpected' => 'ਨਾ-ਸੰਭਾਵਿਤ ਗਣਿਤਫ਼ਲ',
+'formerror' => 'ਦੋਸ਼:ਫ਼ਾਰਮ ਪੇਸ਼ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ',
+'badarticleerror' => 'ਇਹ ਕਾਰਵਾਈ ਇਸ ਸਫ਼ੇ ਤੇ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।',
+'cannotdelete' => "ਫ਼ਾਈਲ ਜਾਂ ਸਫ਼ਾ ''$1'' ਨੂੰ ਮਿਟਾਇਆ ਨਹੀਂ ਜਾ ਸਕਿਆ।
+ਸ਼ਾਇਦ ਕੋਈ ਪਹਿਲਾਂ ਹੀ ਇਸਨੂੰ ਮਿਟਾ ਚੁੱਕਾ ਹੈ।",
+'cannotdelete-title' => "ਸਫ਼ਾ ''$1'' ਨੂੰ ਮਿਟਾਇਆ ਨਹੀਂ ਜਾ ਸਕਿਆ",
+'delete-hook-aborted' => 'ਹੁੱਕ ਨੇ ਮਿਟਾਉਣਾ ਨਾਕਾਮ ਕੀਤਾ।
+ਇਸਨੇ ਕੋਈ ਕਾਰਨ ਨਹੀਂ ਦੱਸਿਆ।',
 'badtitle' => 'ਗ਼ਲਤ ਸਿਰਲੇਖ',
 'badtitletext' => 'ਤੁਹਾਡਾ ਦਰਖ਼ਾਸਤਸ਼ੁਦਾ ਸਿਰਲੇਖ ਨਾਕਾਬਿਲ, ਖ਼ਾਲੀ ਜਾਂ ਗ਼ਲਤ ਜੁੜਿਆ ਹੋਇਆ inter-languagd ਜਾਂ inter-wiki ਸਿਰਲੇਖ ਹੈ। ਇਹ ਵੀ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਸ ਵਿਚ ਇਕ-ਦੋ ਅੱਖਰ ਐਸੇ ਹੋਣ ਜੋ ਸਿਰਲੇਖ ਵਿਚ ਵਰਤੇ ਨਹੀਂ ਜਾ ਸਕਦੇ।',
 'viewsource' => 'ਸਰੋਤ ਵੇਖੋ',
@@ -336,20 +430,32 @@ A list of valid special pages can be found at [[Special:SpecialPages]].',
 'protectedinterface' => 'ਇਹ ਪੰਨਾ ਸਾਫ਼ਟਵੇਅਰ ਇੰਟਰਫ਼ੇਸ ਦਾ ਮੂਲ ਪਾਠ ਹੈ ,ਅਤੇ ਦੁਰਵਰਤੌਂ ਤੌਂ ਬਚਾਅ ਲਈ ਰਾਖਵਾਂ ਕੀਤਾ ਗਿਆ ਹੈ।',
 'editinginterface' => "'''ਚਿਤਾਵਨੀ''' ਤੁਸੀਂ ਐਸੇ ਪੰਨੇ ਨੂੰ ਬਦਲ ਰਹੇ ਹੋ ਜੋ ਸਾਫ਼ਟਵੇਅਰ ਇੰਟਰਫ਼ੇਸ ਦੇ ਮੂਲ ਪਾਠ ਲਈ ਵਰਤਿਆ ਗਿਆ ਹੈ।
 ਇਸ ਪੰਨੇ ਦੇ ਬਦਲਾਅ ਦੁਸਰੇ ਵਰਤੋਂ ਕਰਣ ਵਾਲਿਆਂ ਲਈ ਵਰਤੇ ਜਾਣ ਵਾਲੇ ਇੰਟਰਫਲੇਸ ਦੀ ਸ਼ਕਲ ਤੇ ਅਸਰ ਪਾ ਦੇਣਗੇ।ਅਨੁਵਾਦ ਕਰਣ ਲਈ ,ਕਿਰਪਾ ਕਰਕੇ [//translatewiki.net/wiki/Main_Page?setlang=pa ਟ੍ਰਾਂਸਲੇਟਵਿਕੀ.ਨੈਟ] ਦੀ ਵਰਤੌਂ ਕਰੋ,ਇਹ ਮੀਡੀਆਵਿਕੀ ਦੀ ਸਥਾਨਕੀਕਰਣ ਯੋਜਨਾ ਹੈ।",
+'namespaceprotected' => "ਤੁਹਾਨੂੰ '''$1''' ਥਾਂ-ਨਾਮ ਵਾਲ਼ੇ ਸਫ਼ਿਆਂ ਵਿਚ ਸੋਧ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।",
+'customcssprotected' => 'ਤੁਹਾਨੂੰ ਇਸ CSS ਸਫ਼ੇ ਵਿਚ ਸੋਧ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ ਕਿਉਂਕਿ ਇਸ ਵਿਚ ਕਿਸੇ ਹੋਰ ਮੈਂਬਰ ਦੀਆਂ ਨਿੱਜੀ ਸੈਟਿੰਗਾਂ ਹਨ।',
+'customjsprotected' => 'ਤੁਹਾਨੂੰ ਇਸ CSS ਸਫ਼ੇ ਵਿਚ ਸੋਧ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ ਕਿਉਂਕਿ ਇਸ ਵਿਚ ਕਿਸੇ ਹੋਰ ਮੈਂਬਰ ਦੀਆਂ ਨਿੱਜੀ ਸੈਟਿੰਗਾਂ ਹਨ।',
+'ns-specialprotected' => 'ਖ਼ਾਸ ਸਫ਼ੇ ਸੋਧੇ ਨਹੀਂ ਜਾ ਸਕਦੇ।',
+'titleprotected' => 'ਇਹ ਸਿਰਲੇਖ [[User:$1|$1]] ਵੱਲੋਂ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ ਹੈ ਅਤੇ ਵਰਤਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ। ਦਿੱਤਾ ਹੋਇਆ ਕਾਰਨ ਹੈ, "\'\'$2\'\'"।',
+'invalidtitle-knownnamespace' => 'ਥਾਂ-ਨਾਮ "$2" ਅਤੇ ਲਿਖਤ "$3" ਵਾਲ਼ਾ ਗ਼ਲਤ ਸਿਰਲੇਖ',
+'exception-nologin' => 'ਲਾਗਇਨ ਨਹੀਂ ਕੀਤਾ',
+'exception-nologin-text' => 'ਇਹ ਸਫ਼ਾ ਜਾਂ ਕਾਰਵਾਈ ਤੁਹਾਡਾ ਇਸ ਵਿਕੀ ਤੇ ਲਾਗਇਨ ਹੋਣਾ ਲੋੜਦੀ ਹੈ।',
+
+# Virus scanner
+'virus-unknownscanner' => 'ਅਣਪਛਾਤਾ ਐਂਟੀਵਾਇਰਸ:',
 
 # Login and logout pages
 'logouttext' => "'''ਹੁਣ ਤੁਸੀਂ ਲਾਗਆਉਟ ਹੋ ਗਏ ਹੋ।'''
 
 You can continue to use {{SITENAME}} anonymously, or you can log in again as the same or as a different user.
 Note that some pages may continue to be displayed as if you were still logged in, until you clear your browser cache.",
-'welcomecreation' => '== ਜੀ ਆਇਆਂ ਨੂੰ! ==
+'welcomecreation' => '== ਜੀ ਆਇਆਂ ਨੂੰ, $1! ==
 
-ਤà©\81ਹਾਡਾ à¨\96਼ਾਤਾ à¨¬à¨£ à¨\9aà©\81ੱà¨\95ਾ à¨¹à©\88। à¨\86ਪਣà©\80 [[Special:ਪਸੰਦ|{{SITENAME}} à¨ªà¨¸à©°à¨¦]] à¨¬à¨¦à¨²à¨£à©\80 ਨਾ ਭੁੱਲੋ।',
+ਤà©\81ਹਾਡਾ à¨\96ਾਤਾ à¨¬à¨£ à¨\9aà©\81ੱà¨\95ਾ à¨¹à©\88। à¨\86ਪਣà©\80à¨\86à¨\82 [[Special:Preferences|{{SITENAME}} à¨ªà¨¸à©°à¨¦à¨¾à¨\82]] à¨¬à¨¦à¨²à¨£à©\80à¨\86à¨\82 ਨਾ ਭੁੱਲੋ।',
 'yourname' => 'ਮੈਂਬਰ ਨਾਂ:',
 'yourpassword' => 'ਪਾਸਵਰਡ:',
 'yourpasswordagain' => 'ਪਾਸਵਰਡ ਦੁਬਾਰਾ ਲਿਖੋ:',
 'remembermypassword' => 'ਇਸ ਕੰਪਿਊਟਰ ’ਤੇ ਮੇਰਾ ਲਾਗਇਨ ਯਾਦ ਰੱਖੋ (ਵੱਧ ਤੋਂ ਵੱਧ $1 {{PLURAL:$1|ਦਿਨ|ਦਿਨਾਂ}} ਲਈ)',
 'yourdomainname' => 'ਤੁਹਾਡੀ ਡੋਮੇਨ:',
+'password-change-forbidden' => 'ਇਸ ਵਿਕੀ ਤੇ ਤੁਸੀਂ ਪਾਸਵਰਡ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।',
 'login' => 'ਲਾਗ ਇਨ',
 'nav-login-createaccount' => 'ਲਾਗ ਇਨ/ਖਾਤਾ ਬਣਾਓ',
 'loginprompt' => 'ਤੁਹਾਨੂੰ {{SITENAME}} ’ਤੇ ਲਾਗਇਨ ਕਰਨ ਲਈ ਕੂਕੀਜ਼ ਯੋਗ ਕਰਨੇ ਜ਼ਰੂਰੀ ਹਨ।',
@@ -376,29 +482,32 @@ Note that some pages may continue to be displayed as if you were still logged in
 'noname' => 'ਤੁਸੀਂ ਇੱਕ ਵੈਧ ਯੂਜ਼ਰ ਨਾਂ ਨਹੀਂ ਦਿੱਤਾ ਹੈ।',
 'loginsuccesstitle' => 'ਲਾਗਇਨ ਸਫ਼ਲ ਰਿਹਾ',
 'loginsuccess' => "'''ਤੁਸੀਂ {{SITENAME}} ਉੱਤੇ \"\$1\" ਵਾਂਗ ਲਾਗਇਨ ਕਰ ਚੁੱਕੇ ਹੋ।'''",
-'nosuchuser' => '"$1" ਨਾਂ ਨਾਲ ਕੋਈ ਯੂਜ਼ਰ ਨਹੀਂ ਹੈ। ਆਪਣੇ ਸ਼ਬਦ ਧਿਆਨ ਨਾਲ ਚੈੱਕ ਕਰੋ ਜਾਂ ਨਵਾਂ ਅਕਾਊਂਟ ਬਣਾਓ।',
+'nosuchuser' => '!"$1" ਨਾਂ ਨਾਲ ਕੋਈ ਯੂਜ਼ਰ ਨਹੀਂ ਹੈ। ਆਪਣੇ ਸ਼ਬਦ ਜੋੜ ਧਿਆਨ ਨਾਲ ਚੈਕ ਕਰੋ ਉਪਰ ਹੇਠਾਂ ਦਾ ਕੇਸ ਵਰਤਣ ਨਾਲ ਫ਼ਰਕ ਪੈਂਦਾ ਹੈ ਜਾਂ [[Special:UserLogin/signup|ਨਵਾਂ ਖਾਤਾ ਬਣਾਓ]]',
 'nosuchusershort' => '"$1" ਨਾਂ ਨਾਲ ਕੋਈ ਵੀ ਯੂਜ਼ਰ ਨਹੀਂ ਹੈ। ਆਪਣੇ ਸ਼ਬਦ ਧਿਆਨ ਨਾਲ ਚੈੱਕ ਕਰੋ।',
 'nouserspecified' => 'ਤੁਹਾਨੂੰ ਇੱਕ ਯੂਜ਼ਰ-ਨਾਂ ਦੇਣਾ ਪਵੇਗਾ।',
+'login-userblocked' => 'ਇਹ ਮੈਂਬਰ ਪਾਬੰਦੀਸ਼ੁਦਾ ਹੈ। ਲਾਗਇਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।',
 'wrongpassword' => 'ਗਲਤ ਪਾਸਵਰਡ ਦਿੱਤਾ ਹੈ। ਮੁੜ-ਟਰਾਈ ਕਰੋ ਜੀ।',
 'wrongpasswordempty' => 'ਖਾਲੀ ਪਾਸਵਰਡ ਦਿੱਤਾ ਹੈ। ਮੁੜ-ਟਰਾਈ ਕਰੋ ਜੀ।',
 'passwordtooshort' => 'ਪਾਸਵਰਡ {{PLURAL:$1|1 ਅੱਖਰ|$1 ਅੱਖਰਾਂ}} ਦਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ।',
 'password-name-match' => 'ਤੁਹਾਡਾ ਪਾਸਵਰਡ ਤੁਹਾਡੇ ਯੂਜ਼ਰ ਨਾਂ ਤੋਂ ਵੱਖਰਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।',
+'password-login-forbidden' => 'ਇਹ ਮੈਂਬਰ-ਨਾਮ ਅਤੇ ਪਾਸਵਰਡ ਵਰਤਣ ਦੀ ਮਨਾਹੀ ਹੈ।',
 'mailmypassword' => 'ਨਵਾਂ ਪਾਸਵਰਡ ਈ-ਮੇਲ ਕਰੋ',
 'passwordremindertitle' => '{{SITENAME}} ਲਈ ਪਾਸਵਰਡ ਯਾਦ ਰੱਖੋ',
-'passwordremindertext' => 'ਕਿਸੇ ਨੇ (ਸ਼ਾਇਦ ਤੁਸੀਂ, IP ਐਡਰੈੱਸ $1 ਤੋਂ)
-ਮੰਗ ਕੀਤੀ ਸੀ ਕਿ ਅਸੀਂ ਤੁਹਾਨੂੰ {{SITENAME}} ($4) ਲਈ ਪਾਸਵਰਡ ਭੇਜੀਏ।
-ਯੂਜ਼ਰ "$2" ਲਈ ਹੁਣ ਪਾਸਵਰਡ "$3" ਹੈ।
-ਤੁਹਾਨੂੰ ਹੁਣ ਲਾਗਇਨ ਕਰਕੇ ਆਪਣਾ ਪਾਸਵਰਡ ਹੁਣੇ ਬਦਲਣਾ ਚਾਹੀਦਾ ਹੈ।
-
-If someone else made this request or if you have remembered your password and
-you no longer wish to change it, you may ignore this message and continue using
-your old password.',
+'passwordremindertext' => 'ਕਿਸੇ ਨੇ (ਸ਼ਾਇਦ ਤੁਸੀਂ, IP ਪਤਾ $1 ਤੋਂ) {{SITENAME}} ਲਈ ਪਾਸਵਰਡ ਬਦਲਣ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ ($4)।
+ਮੈਂਬਰ "$2" ਲਈ ਆਰਜ਼ੀ ਪਾਸਵਰਡ ਬਣਾ ਕੇ "$3" ਤੇ ਭੇਜ ਦਿੱਤਾ ਗਿਆ ਹੈ।
+ਜੇ ਤੁਹਾਡਾ ਇਹੀ ਇਰਾਦਾ ਸੀ ਤਾਂ ਤੁਹਾਨੂੰ ਚਾਹੀਦਾ ਹੈ ਹੁਣੇ ਲਾਗਇਨ ਕਰਕੇ ਆਪਣਾ ਪਾਸਵਰਡ ਲਓ।
+ਤੁਹਾਡਾ ਆਰਜ਼ੀ ਪਾਸਵਰਡ {{PLURAL:$5|ਇਕ ਦਿਨ|$5 ਦਿਨਾਂ}} ਵਿਚ ਖ਼ਤਮ ਹੋ ਜਾਵੇਗਾ।
+
+ਜੇ ਕਿਸੇ ਹੋਰ ਨੇ ਇਹ ਬੇਨਤੀ ਕੀਤੀ ਸੀ ਜਾਂ ਜੇ ਤੁਹਾਨੂੰ ਆਪਣਾ ਪਾਸਵਰਡ ਯਾਦ ਹੈ ਅਤੇ ਤੁਸੀਂ ਇਸਨੂੰ ਬਦਲਣਾ ਨਹੀਂ ਚਾਹੁੰਦੇ ਤਾਂ ਤੁਸੀਂ ਇਸ ਸੁਨੇਹੇ ਨੂੰ ਨਜ਼ਰਅੰਦਾਜ਼ ਕਰ ਕੇ ਆਪਣਾ ਪੁਰਾਣਾ ਪਾਸਵਰਡ ਵਰਤਣਾ ਜਾਰੀ ਰੱਖ ਸਕਦੇ ਹੋ।',
 'noemail' => 'ਯੂਜ਼ਰ "$1" ਲਈ ਰਿਕਾਰਡ ਵਿੱਚ ਕੋਈ ਈਮੇਲ ਐਡਰੈੱਸ ਨਹੀਂ ਹੈ।',
 'noemailcreate' => 'ਤੁਹਾਨੂੰ ਠੀਕ ਈਮੇਲ ਐਡਰੈੱਸ ਦੇਣਾ ਪਵੇਗਾ',
 'passwordsent' => '"$1" ਨਾਲ ਰਜਿਸਟਰ ਕੀਤੇ ਈਮੇਲ ਐਡਰੈੱਸ ਉੱਤੇ ਈਮੇਲ ਭੇਜੀ ਗਈ ਹੈ।
 ਇਹ ਮਿਲ ਦੇ ਬਾਅਦ ਮੁੜ ਲਾਗਇਨ ਕਰੋ ਜੀ।',
-'throttled-mailpassword' => 'ਇੱਕ ਪਾਸਵਰਡ ਰੀਮਾਈਡਰ ਪਹਿਲਾਂ ਹੀ ਭੇਜਿਆ ਗਿਆ ਹੈ, ਆਖਰੀ
-$1 ਘੰਟੇ ਵਿੱਚ। ਨੁਕਸਾਨ ਤੋਂ ਬਚਣ ਲਈ, $1 ਘੰਟਿਆਂ ਵਿੱਚ ਇੱਕ ਹੀ ਪਾਸਵਰਡ ਰੀਮਾਈਡਰ ਭੇਜਿਆ ਜਾਂਦਾ ਹੈ।',
+'blocked-mailpassword' => 'ਤੁਹਾਡੇ IP ਪਤੇ ਤੇ ਸੋਧ ਕਰਨ ਤੇ ਪਾਬੰਦੀ ਹੈ ਅਤੇ ਇਸੇ ਕਰਕੇ, ਗ਼ਲਤ ਵਰਤੋਂ ਤੋਂ ਬਚਣ ਲਈ, ਪਾਸਵਰਡ ਹਾਸਲ ਕਰਨ ਵਾਲ਼ੀ ਸਹੂਲਤ ਦੀ ਵਰਤੋਂ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।',
+'eauthentsent' => 'ਇਕ ਤਸਦੀਕੀ ਈ-ਮੇਲ ਨਾਮਜ਼ਦ ਕੀਤੇ ਈ-ਮੇਲ ਪਤੇ ਤੇ ਭੇਜੀ ਜਾ ਚੁੱਕੀ ਹੈ।
+ਤੁਹਾਡੇ ਪਤੇ ਤੇ ਕੋਈ ਹੋਰ ਈ-ਮੇਲ ਭੇਜਣ ਤੋਂ ਪਹਿਲਾਂ, ਇਹ ਤਸਦੀਕ ਕਰਨ ਲਈ ਕਿ ਖਾਤਾ ਅਸਲ ਵਿਚ ਤੁਹਾਡਾ ਹੀ ਹੈ, ਤੁਹਾਨੂੰ ਉਸ ਈ-ਮੇਲ ਵਿਚਲੀਆਂ ਹਦਾਇਤਾਂ ਤੇ ਅਮਲ ਕਰਨਾ ਹੋਵੇਗਾ।',
+'throttled-mailpassword' => 'ਆਖ਼ਰੀ {{PLURAL:$1|ਇੱਕ ਘੰਟੇ|$1 ਘੰਟਿਆਂ}} ਵਿਚ ਇੱਕ ਪਾਸਵਰਡ ਯਾਦ-ਦਹਾਨੀ ਪਹਿਲਾਂ ਹੀ ਭੇਜੀ ਜਾ ਚੁੱਕੀ ਹੈ।
+ਗ਼ਲਤ ਵਰਤੋਂ ਤੋਂ ਬਚਣ ਲਈ, {{PLURAL:$1|ਇੱਕ ਘੰਟੇ|$1 ਘੰਟਿਆਂ}} ਵਿੱਚ ਸਿਰਫ਼ ਇੱਕ ਹੀ ਪਾਸਵਰਡ ਯਾਦ-ਦਹਾਨੀ ਭੇਜੀ ਜਾਂਦੀ ਹੈ।',
 'mailerror' => 'ਈਮੇਲ ਭੇਜਣ ਦੌਰਾਨ ਗਲਤੀ: $1',
 'acct_creation_throttle_hit' => 'ਅਫਸੋਸ ਹੈ, ਪਰ ਤੁਸੀਂ ਪਹਿਲਾਂ ਹੀ $1 ਅਕਾਊਂਟ ਬਣਾ ਚੁੱਕੇ ਹੋ। ਤੁਸੀਂ ਹੋਰ ਨਹੀਂ ਬਣਾ ਸਕਦੇ।',
 'emailauthenticated' => 'ਤੁਹਾਡਾ ਈਮੇਲ ਐਡਰੈੱਸ $1 ਉੱਤੇ ਪਰਮਾਣਿਤ ਕੀਤਾ ਗਿਆ ਹੈ।',
@@ -406,11 +515,24 @@ $1 ਘੰਟੇ ਵਿੱਚ। ਨੁਕਸਾਨ ਤੋਂ ਬਚਣ ਲਈ, $
 'noemailprefs' => 'ਇਹ ਫੀਚਰ ਵਰਤਣ ਲਈ ਇੱਕ ਈਮੇਲ ਐਡਰੈੱਸ ਦਿਓ।।',
 'emailconfirmlink' => 'ਆਪਣਾ ਈ-ਮੇਲ ਐਡਰੈੱਸ ਕਨਫਰਮ ਕਰੋ।',
 'invalidemailaddress' => 'ਈਮੇਲ ਐਡਰੈੱਸ ਮਨਜ਼ੂਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ ਕਿਉਂਕਿ ਇਹ ਠੀਕ ਫਾਰਮੈਟ ਨਹੀਂ ਜਾਪਦਾ ਹੈ। ਇੱਕ ਠੀਕ ਫਾਰਮੈਟ ਵਿੱਚ ਦਿਓ ਜਾਂ ਇਹ ਖੇਤਰ ਖਾਲੀ ਛੱਡ ਦਿਓ।',
+'cannotchangeemail' => 'ਇਸ ਵਿਕੀ ਤੇ ਈ-ਮੇਲ ਪਤੇ ਬਦਲੇ ਨਹੀਂ ਜਾ ਸਕਦੇ।',
+'emaildisabled' => 'ਇਹ ਸਾਈਟ ਈ-ਮੇਲਾਂ ਨਹੀਂ ਭੇਜ ਸਕਦੀ।',
 'accountcreated' => 'ਅਕਾਊਂਟ ਬਣਾਇਆ',
 'accountcreatedtext' => '$1 ਲਈ ਯੂਜ਼ਰ ਅਕਾਊਂਟ ਬਣਾਇਆ ਗਿਆ।',
 'createaccount-title' => '{{SITENAME}} ਲਈ ਅਕਾਊਂਟ ਬਣਾਉਣਾ',
+'createaccount-text' => 'ਕਿਸੇ ਨੇ "$2" ਮੈਂਬਰ-ਨਾਮ ਅਤੇ "$3" ਪਾਸਵਰਡ ਨਾਲ਼ {{SITENAME}} ($4) ਤੇ, ਤੁਹਾਡਾ ਈ-ਮੇਲ ਪਤਾ ਵਰਤਦੇ ਹੋਏ, ਖਾਤਾ ਬਣਾਇਆ ਹੈ।
+ਤੁਹਾਨੂੰ ਹੁਣੇ ਲਾਗਇਨ ਕਰਕੇ ਆਪਣਾ ਪਾਸਵਰਡ ਬਦਲਣਾ ਚਾਹੀਦਾ ਹੈ।
+
+ਜੇ ਇਹ ਖਾਤਾ ਗ਼ਲਤੀ ਨਾਲ਼ ਬਣ ਗਿਆ ਹੈ ਤਾਂ ਤੁਸੀਂ ਇਸ ਸੁਨੇਹੇ ਨੂੰ ਨਜ਼ਰਅੰਦਾਜ਼ ਕਰ ਸਕਦੇ ਹੋ।',
+'usernamehasherror' => 'ਮੈਂਬਰ-ਨਾਮ ਵਿਚ ਹੈਸ਼ ਅੱਖਰ ਨਹੀਂ ਹੋ ਸਕਦੇ',
+'login-throttled' => 'ਤੁਸੀਂ ਬਹੁਤ ਸਾਰੀਆਂ ਤਾਜ਼ਾ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ਾਂ ਕੀਤੀਆਂ ਹਨ।
+ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਥੋੜੀ ਉਡੀਕ ਕਰੋ।',
+'login-abort-generic' => 'ਤੁਹਾਡੀ ਲਾਗਇਨ ਨਾਕਾਮ ਸੀ - ਰੱਦ',
 'loginlanguagelabel' => 'ਬੋਲੀ: $1',
 
+# E-mail sending
+'user-mail-no-addy' => 'ਬਿਨਾਂ ਈ-ਮੇਲ ਪਤਾ ਦਿੱਤੇ ਈ-ਮੇਲ ਭੇਜਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ।',
+
 # Change password dialog
 'resetpass' => 'ਪਾਸਵਰਡ ਬਦਲੋ',
 'resetpass_announce' => 'ਤੁਸੀਂ ਇੱਕ ਆਰਜ਼ੀ ਈ-ਮੇਲ ਕੀਤੇ ਕੋਡ ਨਾਲ ਲਾਗਇਨ ਕੀਤਾ ਹੈ। ਲਾਗਇਨ ਪੂਰਾ ਕਰਨ ਲਈ, ਤੁਹਾਨੂੰ ਇੱਥੇ ਨਵਾਂ ਪਾਸਵਰਡ ਦੇਣਾ ਪਵੇਗਾ:',
@@ -421,10 +543,46 @@ $1 ਘੰਟੇ ਵਿੱਚ। ਨੁਕਸਾਨ ਤੋਂ ਬਚਣ ਲਈ, $
 'resetpass_submit' => 'ਪਾਸਵਰਡ ਸੈੱਟ ਕਰੋ ਅਤੇ ਲਾਗਇਨ ਕਰੋ',
 'resetpass_success' => 'ਤੁਹਾਡਾ ਪਾਸਵਰਡ ਠੀਕ ਤਰਾਂ ਬਦਲਿਆ ਗਿਆ ਹੈ! ਹੁਣ ਤੁਸੀਂ ਲਾਗਇਨ ਕਰ ਸਕਦੇ ਹੋ...',
 'resetpass_forbidden' => 'ਪਾਸਵਰਡ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ',
+'resetpass-no-info' => 'ਇਸ ਸਫ਼ੇ ਨੂੰ ਸਿੱਧੇ ਹੀ ਵੇਖਣ ਲਈ ਤੁਹਾਨੂੰ ਲਾਗਇਨ ਕਰਨਾ ਪਵੇਗਾ।',
 'resetpass-submit-loggedin' => 'ਪਾਸਵਰਡ ਬਦਲੋ',
 'resetpass-submit-cancel' => 'ਰੱਦ ਕਰੋ',
+'resetpass-wrong-oldpass' => 'ਗ਼ਲਤ ਆਰਜ਼ੀ ਜਾਂ ਚਾਲੂ ਪਾਸਵਰਡ।
+ਸ਼ਾਇਦ ਤੁਸੀਂ ਕਾਮਯਾਬੀ ਨਾਲ਼ ਆਪਣਾ ਪਾਸਵਰਡ ਬਦਲ ਚੁੱਕੇ ਹੋ ਜਾਂ ਆਰਜ਼ੀ ਪਾਸਵਰਡ ਲਈ ਬੇਨਤੀ ਕੀਤੀ ਸੀ।',
 'resetpass-temp-password' => 'ਆਰਜ਼ੀ ਪਾਸਵਰਡ:',
 
+# Special:PasswordReset
+'passwordreset' => 'ਪਾਸਵਰਡ ਰੀਸੈੱਟ ਕਰੋ',
+'passwordreset-text' => 'ਆਪਣੇ ਖਾਤੇ ਦੀ ਤਫ਼ਸੀਲ ਦੀ ਈ-ਮੇਲ ਹਾਸਲ ਕਰਨ ਲਈ ਇਹ ਫ਼ਾਰਮ ਮੁਕੰਮਲ ਕਰੋ।',
+'passwordreset-legend' => 'ਪਾਸਵਰਡ ਰੀਸੈੱਟ ਕਰੋ',
+'passwordreset-disabled' => 'ਇਸ ਵਿਕੀ ਤੇ ਪਾਸਵਰਡ ਰੀਸੈੱਟ ਬੰਦ ਕੀਤੇ ਗਏ ਹਨ।',
+'passwordreset-username' => 'ਮੈਂਬਰ-ਨਾਂ:',
+'passwordreset-domain' => 'ਡੋਮੇਨ:',
+'passwordreset-email' => 'ਈ-ਮੇਲ ਪਤਾ:',
+'passwordreset-emailtitle' => '{{SITENAME}} ਤੇ ਖਾਤੇ ਦੀ ਜਾਣਕਾਰੀ',
+'passwordreset-emailtext-ip' => 'ਕਿਸੇ ਨੇ (ਸ਼ਾਇਦ ਤੁਸੀਂ, IP ਪਤਾ $1 ਤੋਂ) {{SITENAME}}
+($4) ਲਈ ਖਾਤਾ ਤਫ਼ਸੀਲ ਯਾਦ-ਦਹਾਨੀ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ। ਇਹ {{PLURAL:
+$3|ਖਾਤਾ ਇਸ ਈ-ਮੇਲ ਪਤੇ ਨਾਲ਼ ਜੁੜਿਆ ਹੈ|ਖਾਤੇ ਇਸ ਈ-ਮੇਲ ਪਤੇ ਨਾਲ਼ ਜੁੜੇ ਹਨ}}:
+$2
+
+ਇਹ ਆਰਜ਼ੀ ਪਾਸਵਰਡ
+{{PLURAL:$5|ਇੱਕ ਦਿਨ|$5 ਦਿਨਾਂ}} ਵਿਚ ਖ਼ਤਮ ਹੋ {{PLURAL:$3|ਜਾਵੇਗਾ|ਜਾਣਗੇ}}।
+ਤੁਹਾਨੂੰ ਹੁਣੇ ਲਾਗਇਨ ਕਰਕੇ ਨਵਾਂ ਪਾਸਵਰਡ ਬਣਾਉਣਾ ਚਾਹੀਦਾ ਹੈ। ਜੇ ਕਿਸੇ ਹੋਰ ਨੇ ਇਹ ਬੇਨਤੀ ਕੀਤੀ ਸੀ ਜਾਂ ਜੇ ਤੁਹਾਨੂੰ ਆਪਣਾ ਪਾਸਵਰਡ ਯਾਦ ਹੈ ਅਤੇ ਤੁਸੀਂ ਇਸਨੂੰ ਬਦਲਣਾ ਨਹੀਂ ਚਾਹੁੰਦੇ ਤਾਂ ਤੁਸੀਂ ਇਸ ਸੁਨੇਹੇ ਨੂੰ ਨਜ਼ਰਅੰਦਾਜ਼ ਕਰ ਕੇ ਆਪਣਾ ਪੁਰਾਣਾ ਪਾਸਵਰਡ ਵਰਤਣਾ ਜਾਰੀ ਰੱਖ ਸਕਦੇ ਹੋ।',
+'passwordreset-emailelement' => 'ਮੈਂਬਰ-ਨਾਂ: $1
+ਆਰਜ਼ੀ ਪਾਸਵਰਡ: $2',
+'passwordreset-emailsent' => 'ਇੱਕ ਯਾਦ-ਦਹਾਨੀ ਈ-ਮੇਲ ਭੇਜੀ ਜਾ ਚੁੱਕੀ ਹੈ।',
+'passwordreset-emailsent-capture' => 'ਇੱਕ ਯਾਦ-ਦਹਾਨੀ ਈ-ਮੇਲ, ਜਿਹੜੀ ਕਿ ਹੇਠਾਂ ਦਿੱਸ ਰਹੀ ਹੈ, ਭੇਜੀ ਜਾ ਚੁੱਕੀ ਹੈ।',
+
+# Special:ChangeEmail
+'changeemail' => 'ਈ-ਮੇਲ ਪਤਾ ਬਦਲੋ',
+'changeemail-header' => 'ਖਾਤੇ ਵਾਲ਼ਾ ਈ-ਮੇਲ ਪਤਾ ਬਦਲੋ',
+'changeemail-text' => 'ਆਪਣਾ ਈ-ਮੇਲ ਪਤਾ ਬਦਲਣ ਲਈ ਇਹ ਫ਼ਾਰਮ ਮੁਕੰਮਲ ਕਰੋ। ਇਸ ਤਬਦੀਲੀ ਨੂੰ ਤਸਦੀਕ ਕਰਨ ਲਈ ਤੁਹਾਨੂੰ ਆਪਣਾ ਪਾਸਵਰਡ ਦਾਖ਼ਲ ਕਰਨਾ ਪਵੇਗਾ।',
+'changeemail-no-info' => 'ਇਸ ਸਫ਼ੇ ਨੂੰ ਸਿੱਧੇ ਹੀ ਵੇਖਣ ਲਈ ਤੁਹਾਨੂੰ ਲਾਗਇਨ ਕਰਨਾ ਪਵੇਗਾ।',
+'changeemail-oldemail' => 'ਚਾਲੂ ਈ-ਮੇਲ ਪਤਾ:',
+'changeemail-newemail' => 'ਨਵਾਂ ਈ-ਮੇਲ ਪਤਾ:',
+'changeemail-none' => '(ਕੋਈ ਨਹੀਂ)',
+'changeemail-submit' => 'ਈ-ਮੇਲ ਬਦਲੋ',
+'changeemail-cancel' => 'ਰੱਦ ਕਰੋ',
+
 # Edit page toolbar
 'bold_sample' => 'ਗੂੜ੍ਹੀ ਲਿਖਾਈ',
 'bold_tip' => 'ਗੁੜ੍ਹੀ ਲਿਖਾਈ',
@@ -454,12 +612,35 @@ $1 ਘੰਟੇ ਵਿੱਚ। ਨੁਕਸਾਨ ਤੋਂ ਬਚਣ ਲਈ, $
 'showlivepreview' => 'ਲਾਈਵ ਝਲਕ',
 'showdiff' => 'ਤਬਦੀਲੀ ਵੇਖੋ',
 'anoneditwarning' => "'''ਚੇਤਾਵਨੀ:''' ਤੁਸੀਂ ਲਾਗਇਨ ਨਹੀਂ ਕੀਤਾ ਹੈ। ਤੁਹਾਡਾ IP ਐਡਰੈੱਸ ਇਸ ਸਫ਼ੇ ਦੇ ਅਤੀਤ ਵਿੱਚ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਵੇਗਾ।",
+'anonpreviewwarning' => "''ਤੁਸੀਂ ਲਾਗਇਨ ਨਹੀਂ ਕੀਤਾ। ਤਬਦੀਲੀ ਸਾਂਭਣ ਨਾਲ਼ ਤੁਹਾਡਾ IP ਪਤਾ ਸਫ਼ੇ ਦੇ ਸੋਧ ਅਤੀਤ ਵਿਚ ਰਿਕਾਰਡ ਹੋ ਜਾਵੇਗਾ।''",
+'missingsummary' => "'''ਯਾਦ-ਦਹਾਨੀ:''' ਤੁਸੀਂ ਸੋਧ ਸਾਰ ਮੁਹੱਈਆ ਨਹੀਂ ਕਰਵਾਇਆ। ਜੇ ਤੁਸੀਂ \"{{int:savearticle}}\" ਤੇ ਦੁਬਾਰਾ ਕਲਿੱਕ ਕੀਤਾ ਤਾਂ ਤੁਹਾਡਾ ਸਫ਼ਾ ਇਸਦੇ ਬਿਨਾਂ ਹੀ ਸਾਂਭਿਆ ਜਾਵੇਗਾ।",
 'missingcommenttext' => 'ਹੇਠਾਂ ਇੱਕ ਟਿੱਪਣੀ ਦਿਓ।',
 'summary-preview' => 'ਸੰਖੇਪ ਝਲਕ:',
 'subject-preview' => 'ਵਿਸ਼ਾ/ਹੈੱਡਲਾਈਨ ਝਲਕ:',
 'blockedtitle' => 'ਯੂਜ਼ਰ ਬਲਾਕ ਕੀਤਾ ਗਿਆ',
+'blockedtext' => "'''ਤੁਹਾਡੇ ਮੈਂਬਰ-ਨਾਂ ਜਾਂ IP ਪਤੇ ਉੱਤੇ ਪਾਬੰਦੀ ਲੱਗ ਚੁੱਕੀ ਹੈ।'''
+
+ਪਾਬੰਦੀ $1 ਨੇ ਲਾਈ ਹੈ।
+ਦਿੱਤਾ ਗਿਆ ਕਾਰਨ ਇਹ ਹੈ, ''$2''।
+
+* ਪਾਬੰਦੀ ਸ਼ੁਰੂ: $8
+* ਪਾਬੰਦੀ ਖ਼ਤਮ: $6
+* ਪਾਬੰਦੀ ਲਾਉਣ ਵਾਲ਼ੇ ਦਾ ਇਰਾਦਾ: $7
+
+ਪਾਬੰਦੀ ਬਾਰੇ ਚਰਚਾ ਕਰਨ ਲਈ ਤੁਸੀਂ $1 ਜਾਂ ਕਿਸੇ ਹੋਰ
+[[{{MediaWiki:Grouppage-
+sysop}}|administrator]] ਨਾਲ਼ ਰਾਬਤਾ ਕਰ ਸਕਦੇ ਹੋ।
+ਤੁਸੀਂ 'ਇਸ ਮੈਂਬਰ ਨੂੰ ਈ-ਮੇਲ ਭੇਜੋ' ਸਹੂਲਤ ਦੀ ਵਰਤੋਂ ਨਹੀਂ ਕਰ ਸਕਦੇ ਜੇ ਤੁਹਾਡੀਆਂ [[Special:Preferences|ਖਾਤਾ ਪਸੰਦਾਂ]] ਵਿਚ ਇੱਕ ਸਹੀ ਈ-ਮੇਲ ਪਤਾ ਨਹੀਂ ਦਿੱਤਾ ਗਿਆ ਅਤੇ ਜੇ ਤੁਹਾਡੇ ਇਸਨੂੰ ਵਰਤਣ ਤੇ ਪਾਬੰਦੀ ਹੈ।
+ਤੁਹਾਡਾ ਚਾਲੂ IP ਪਤਾ $3 ਹੈ,
+ਅਤੇ ਪਾਬੰਦੀ ਪਤਾ #$5 ਹੈ।
+ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਆਪਣੇ ਕਿਸੇ ਵੀ ਸਵਾਲ ਜਾਂ ਪੁੱਛ-ਗਿੱਛ ਵਿਚ ਇਹ ਉੱਪਰਲੀ ਤਫ਼ਸੀਲ ਜ਼ਰੂਰ ਸ਼ਾਮਲ ਕਰੋ।",
+'blockednoreason' => 'ਕੋਈ ਕਾਰਨ ਨਹੀਂ ਦੱਸਿਆ ਗਿਆ',
 'whitelistedittext' => 'ਪੇਜ ਸੋਧਣ ਲਈ ਤੁਹਾਨੂੰ $1 ਕਰਨਾ ਪਵੇਗਾ।',
+'confirmedittext' => 'ਸਫ਼ਿਆਂ ਨੂੰ ਸੋਧਣ ਤੋਂ ਪਹਿਲਾਂ ਤੁਹਾਨੂੰ ਆਪਣਾ ਈ-ਮੇਲ ਪਤਾ ਤਸਦੀਕ ਕਰਨਾ ਪਵੇਗਾ।
+ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਆਪਣੀਆਂ [[Special:Preferences|ਖਾਤਾ ਪਸੰਦਾ]] ਜ਼ਰੀਏ ਸਹੀ ਈ-ਮੇਲ ਪਤਾ ਦਿਓ ਅਤੇ ਤਸਦੀਕ ਕਰੋ।',
 'nosuchsectiontitle' => 'ਇੰਝ ਦਾ ਕੋਈ ਸ਼ੈਕਸ਼ਨ ਨਹੀਂ ਹੈ।',
+'nosuchsectiontext' => 'ਤੁਸੀਂ ਨਾ-ਮੌਜੂਦ ਸੈਕਸ਼ਨ ਨੂੰ ਸੋਧਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਹੈ।
+ਸ਼ਾਇਦ ਤੁਹਾਡੇ ਸਫ਼ੇ ਨੂੰ ਵੇਖਣ ਦੇ ਦੌਰਾਨ ਇਹ ਮਿਟਾਇਆ ਜਾਂ ਇਸਦਾ ਸਿਰਲੇਖ ਬਦਲਿਆ ਜਾ ਚੁੱਕਾ ਹੈ।',
 'loginreqtitle' => 'ਲਾਗਇਨ ਚਾਹੀਦਾ ਹੈ',
 'loginreqlink' => 'ਲਾਗਇਨ',
 'loginreqpagetext' => 'ਹੋਰ ਪੇਜ ਵੇਖਣ ਲਈ ਤੁਹਾਨੂੰ $1 ਕਰਨਾ ਪਵੇਗਾ।',
@@ -469,18 +650,52 @@ $1 ਘੰਟੇ ਵਿੱਚ। ਨੁਕਸਾਨ ਤੋਂ ਬਚਣ ਲਈ, $
 'newarticletext' => "ਤੁਸੀਂ ਕਿਸੇ ਐਸੇ ਲਿੰਕ ਰਾਹੀਂ ਇਸ ਸਫ਼ੇ ’ਤੇ ਪੁੱਜੇ ਹੋ ਜੋ ਹਾਲੇ ਬਣਾਇਆ ਨਹੀਂ ਗਿਆ।
 ਸਫ਼ਾ ਬਣਾਉਣ ਲਈ ਹੇਠ ਦਿੱਤੇ ਖ਼ਾਨੇ ਵਿਚ ਲਿਖਣਾ ਸ਼ੁਰੂ ਕਰੋ। (ਹੋਰ ਮਦਦ ਲਈ [[{{MediaWiki:Helppage}}|ਮਦਦ ਸਫ਼ਾ]] ਵੇਖੋ)
 ਜੇ ਤੁਸੀਂ ਗ਼ਲਤੀ ਨਾਲ਼ ਇੱਥੇ ਆਏ ਹੋ ਤਾਂ ਆਪਣੇ ਬਰਾਊਜ਼ਰ ਦੇ ''ਪਿੱਛੇ'' (back) ਬਟਨ ’ਤੇ ਕਲਿੱਕ ਕਰੋ।",
+'anontalkpagetext' => "----''ਇਹ ਇਕ ਗੁਮਨਾਮ ਮੈਂਬਰ ਲਈ ਇਕ ਚਰਚਾ ਸਫ਼ਾ ਹੈ ਜਿਸਨੇ ਹਾਲੇ ਖਾਤਾ ਨਹੀ ਬਣਾਇਆ ਜਾਂ ਉਸਨੂੰ ਵਰਤ ਨਹੀਂ ਰਿਹਾ।
+ਇਸ ਵਾਸਤੇ ਸਾਡੇ ਕੋਲ ਉਸਨੂੰ ਪਛਾਨਣ ਲਈ IP ਪਤਾ ਹੈ।
+ਇਕ IP ਪਤਾ ਕਈ ਵਰਤਣ ਵਾਲ਼ਿਆਂ ਦੁਆਰਾ ਸਾਂਝਾ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ।
+ਜੇ ਤੁਸੀਂ ਇੱਕ ਗੁਮਨਾਮ ਮੈਂਬਰ ਹੋ ਅਤੇ ਸਮਝਦੇ ਹੋ ਕਿ ਇਹ ਟਿੱਪਣੀਆਂ ਤੁਹਾਡੇ ਲਈ ਹਨ ਤਾਂ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਹੋਰਾਂ ਗੁਮਨਾਮ ਮੈਂਬਰਾਂ ਨਾਲ਼ ਪੈਦਾ ਹੋਣ ਵਾਲ਼ੀ ਉਲਝਣ ਤੋਂ ਬਚਣ ਲਈ [[Special:UserLogin/signup|ਖਾਤਾ ਬਣਾਓ]] ਜਾਂ [[Special:UserLogin|ਲਾਗਇਨ ਕਰੋ]]।''",
 'noarticletext' => 'ਫ਼ਿਲਹਾਲ ਇਸ ਸਫ਼ੇ ’ਤੇ ਕੋਈ ਲਿਖਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਦੂਜੇ ਸਫ਼ਿਆਂ ’ਤੇ [[Special:Search/{{PAGENAME}}|ਇਸ ਸਿਰਲੇਖ ਦੀ ਖੋਜ]] ਕਰ ਸਕਦੇ ਹੋ, <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ਸਬੰਧਿਤ ਚਿੱਠੇ ਖੋਜ] ਸਕਦੇ ਹੋ ਜਾਂ ਇਸ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ਸਫ਼ੇ ਵਿਚ ਲਿਖ] ਸਕਦੇ ਹੋ</span>।',
 'noarticletext-nopermission' => 'ਫ਼ਿਲਹਾਲ ਇਸ ਸਫ਼ੇ ’ਤੇ ਕੋਈ ਲਿਖਤ ਨਹੀਂ ਹੈ। ਤੁਸੀਂ ਦੂਸਰੇ ਸਫ਼ਿਆਂ ਤੇ [[Special:Search/{{PAGENAME}}|ਇਸ ਪਾਠ ਦੀ ਖੋਜ]] ਕਰ ਸਕਦੇ ਹੋ, ਸਬੰਧਤ <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ਚਿੱਠੇ] ਖੋਜ ਸਕਦੇ ਹੋ, ਜਾਂ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ਇਸ ਸਫ਼ੇ ਵਿਚ ਲਿਖ] ਸਕਦੇ ਹੋ</span>।',
+'userpage-userdoesnotexist' => 'ਮੈਂਬਰ ਖਾਤਾ "$1" ਰਜਿਸਟਰ ਨਹੀਂ ਹੈ।
+ਜੇ ਤੁਸੀਂ ਇਸਨੂੰ ਬਣਾਉਣਾ/ਸੋਧਣਾ ਚਾਹੁੰਦੇ ਹੋ ਤਾਂ ਮਿਰਬਾਨੀ ਕਰਕੇ ਜਾਂਚ ਕਰ ਲਓ।',
+'userpage-userdoesnotexist-view' => 'ਮੈਂਬਰ ਖਾਤਾ "$1" ਰਜਿਸਟਰ ਨਹੀਂ ਹੈ।',
+'blocked-notice-logextract' => 'ਇਹ ਮੈਂਬਰ ਇਸ ਵੇਲ਼ੇ ਪਾਬੰਦੀਸ਼ੁਦਾ ਹੈ।
+ਹਵਾਲੇ ਲਈ ਪਾਬੰਦੀ ਚਿੱਠੇ ਦਾ ਨਵਾਂ ਦਾਖ਼ਲਾ ਹੇਠ ਦਿੱਤਾ ਗਿਆ ਹੈ:',
+'usercssyoucanpreview' => "'''ਟੋਟਕਾ:''' ਆਪਣੇ ਨਵੇਂ CSS ਸਫ਼ੇ ਨੂੰ ਸਾਂਭਣ ਤੋਂ ਪਹਿਲਾਂ ਪਰਖ ਕਰਨ ਲਈ \"{{int:showpreview}}\" ਬਟਨ ਵਰਤੋ।",
+'userjsyoucanpreview' => "'''ਟੋਟਕਾ:''' ਆਪਣੀ ਜਾਵਾਸਕ੍ਰਿਪਟ ਨੂੰ ਸਾਂਭਣ ਤੋਂ ਪਹਿਲਾਂ ਪਰਖ ਕਰਨ ਲਈ \"{{int:showpreview}}\" ਬਟਨ ਵਰਤੋ।",
+'usercsspreview' => "'''ਯਾਦ ਰੱਖੋ ਤੁਸੀਂ ਆਪਣੀ ਮੈਂਬਰ CSS ਦੀ ਸਿਰਫ਼ ਇਕ ਝਲਕ ਵੇਖ ਰਹੇ ਹੋ।'''
+'''ਇਹ ਹਾਲੇ ਸਾਂਭੀ ਨਹੀਂ ਗਈ ਹੈ!'''",
+'userjspreview' => "'''ਯਾਦ ਰੱਖੋ ਤੁਸੀਂ ਆਪਣੀ ਮੈਂਬਰ ਜਾਵਾਸਕ੍ਰਿਪਟ ਦੀ ਸਿਰਫ਼ ਇਕ ਪਰਖ/ਝਲਕ ਵੇਖ ਰਹੇ ਹੋ।'''
+'''ਇਹ ਹਾਲੇ ਸਾਂਭੀ ਨਹੀਂ ਗਈ ਹੈ!'''",
+'sitecsspreview' => "'''ਯਾਦ ਰੱਖੋ ਤੁਸੀਂ ਇਸ CSS ਦੀ ਸਿਰਫ਼ ਇਕ ਝਲਕ ਵੇਖ ਰਹੇ ਹੋ।'''
+'''ਇਹ ਹਾਲੇ ਸਾਂਭੀ ਨਹੀਂ ਗਈ ਹੈ!'''",
+'sitejspreview' => "'''ਯਾਦ ਰੱਖੋ ਤੁਸੀਂ ਇਸ ਜਾਵਾਸਕ੍ਰਿਪਟ ਕੋਡ ਦੀ ਸਿਰਫ਼ ਇਕ ਝਲਕ ਵੇਖ ਰਹੇ ਹੋ।'''
+'''ਇਹ ਹਾਲੇ ਸਾਂਭੀ ਨਹੀਂ ਗਈ ਹੈ!'''",
 'updated' => '(ਅੱਪਡੇਟ)',
 'note' => "'''ਨੋਟ:'''",
 'previewnote' => 'ਯਾਦ ਰੱਖੋ ਇਹ ਸਿਰਫ਼ ਇੱਕ ਝਲਕ ਹੈ; ਤੁਹਾਡੀਆਂ ਤਬਦੀਲੀਆਂ ਹਾਲੇ ਸਾਂਭੀਆਂ ਨਹੀਂ ਗਈਆਂ!',
+'continue-editing' => 'ਸੋਧਣਾ ਜਾਰੀ ਰੱਖੋ',
+'previewconflict' => 'ਇਹ ਝਲਕ ਲਿਖਤ ਦਾ ਓਹ ਅਕਸ ਪੇਸ਼ ਕਰਦੀ ਹੈ ਜਿਵੇਂ ਓਹ ਤੁਹਾਡੇ ਸਾਂਭੇ ਜਾਣ ਤੋਂ ਬਾਅਦ ਦਿੱਸੇਗਾ।',
 'editing' => '$1 ਸੋਧਿਆ ਜਾ ਰਿਹਾ ਹੈ',
+'creating' => '$1 ਬਣਾ ਰਹੇ ਹੋ',
 'editingsection' => '$1 ਜ਼ੇਰੇ ਸੁਧਾਈ ਹੈ (ਸ਼ੈਕਸ਼ਨ)',
 'editingcomment' => '$1 (ਟਿੱਪਣੀ) ਸੋਧ',
 'editconflict' => 'ਅਪਵਾਦ ਟਿੱਪਣੀ: $1',
 'yourtext' => 'ਤੁਹਾਡਾ ਟੈਕਸਟ',
 'storedversion' => 'ਸੰਭਾਲਿਆ ਵਰਜਨ',
 'yourdiff' => 'ਅੰਤਰ',
+'longpageerror' => "'''ਗ਼ਲਤੀ: ਤੁਹਾਡੀ ਪੇਸ਼ ਕੀਤੀ ਲਿਖਤ {{PLURAL:$1|ਇੱਕ ਕਿਲੋਬਾਈਟ|$1 ਕਿਲੋਬਾਈਟ}} ਦੀ ਹੈ ਜੋ ਕਿ {{PLURAL:$2|ਇੱਕ ਕਿਲੋਬਾਈਟ|$2 ਕਿਲੋਬਾਈਟ}} ਦੇ ਵੱਧ ਤੋਂ ਵੱਧ ਅਕਾਰ ਤੋਂ ਜ਼ਿਆਦਾ ਹੈ।'''
+ਇਹ ਸਾਂਭੀ ਨਹੀਂ ਜਾ ਸਕਦੀ।",
+'readonlywarning' => "'''ਖ਼ਬਰਦਾਰ: ਡੈਟਾਬੇਸ ਰੱਖ-ਰਖਾਵ ਦੇ ਕਰਕੇ ਤਾਲਾ-ਬੱਧ ਹੈ ਇਸ ਕਰਕੇ ਤੁਸੀਂ ਹੁਣੇ ਆਪਣੀ ਤਬਦੀਲੀ ਨਹੀਂ ਸਾਂਭ ਸਕਦੇ।'''
+ਸ਼ਾਇਦ ਤੁਸੀਂ ਇਸ ਲਿਖਤ ਨੂੰ ਕੱਟ ਅਤੇ ਪੇਸਟ ਕਰ ਕੇ ਇਕ ਫ਼ਾਈਲ ਵਜੋਂ ਬਾਅਦ ਵਿਚ ਵਰਤਣ ਲਈ ਸਾਂਭਣਾ ਚਾਹੋਗੇ।
+
+ਜਿਹੜੇ ਪ੍ਰਬੰਧਕ ਨੇ ਇਸਨੂੰ ਤਾਲਾ ਲਾਇਆ ਹੈ ਉਸਦਾ ਕਹਿਣਾ ਹੈ ਕਿ: $1",
+'protectedpagewarning' => "'''ਖ਼ਬਰਦਾਰ: ਇਹ ਸਫ਼ਾ ਸੁਰੱਖਿਅਤ ਹੈ ਜਿਸ ਕਰਕੇ ਸਿਰਫ਼ ਐਡਮਨਿਸਟ੍ਰੇਟਰ ਹੱਕ ਵਾਲ਼ੇ ਮੈਂਬਰ ਹੀ ਇਸ ਨੂੰ ਸੋਧ ਸਕਦੇ ਹਨ।'''
+ਚਿੱਠੇ ਦਾ ਨਵਾਂ ਦਾਖ਼ਲਾ ਹਵਾਲੇ ਲਈ ਹੇਠਾਂ ਦਿੱਤਾ ਗਿਆ ਹੈ:",
+'semiprotectedpagewarning' => "'''ਨੋਟ:''' ਇਹ ਸਫ਼ਾ ਸੁਰੱਖਿਅਤ ਹੈ ਤਾਂ ਕਿ ਸਿਰਫ਼ ਰਜਿਸਟਰ ਹੋਏ ਮੈਂਬਰ ਹੀ ਇਸ ਨੂੰ ਸੋਧ ਸਕਣ।
+ਚਿੱਠੇ ਵਿਚਲਾ ਨਵਾਂ ਦਾਖ਼ਲਾ ਹਵਾਲੇ ਲਈ ਹੇਠਾਂ ਦਿੱਤਾ ਗਿਆ ਹੈ:",
+'titleprotectedwarning' => "'''ਖ਼ਬਰਦਾਰ: ਇਹ ਸਫ਼ਾ ਸੁਰੱਖਿਅਤ ਹੈ ਸੋ ਇਸਨੂੰ ਬਣਾਉਣ ਲਈ [[Special:ListGroupRights|ਖ਼ਾਸ ਹੱਕਾਂ]] ਦੀ ਲੋੜ ਹੈ।'''
+ਚਿੱਠੇ ਦਾ ਨਵਾਂ ਦਾਖ਼ਲਾ ਹਵਾਲੇ ਲਈ ਹੇਠਾਂ ਦਿੱਤਾ ਗਿਆ ਹੈ:",
 'templatesused' => 'ਇਸ ਸਫੇ ’ਤੇ {{PLURAL:$1|ਵਰਤਿਆ ਸਾਂਚਾ|ਵਰਤੇ ਸਾਂਚੇ}}:',
 'templatesusedpreview' => "{{PLURAL:$1|ਟੈਪਲੇਟ|ਟੈਪਲੇਟ}} ਇਹ ਝਲਕ 'ਚ ਵਰਤੇ ਜਾਂਦੇ ਹਨ:",
 'templatesusedsection' => 'ਇਹ ਸ਼ੈਕਸ਼ਨ ਵਿੱਚ ਟੈਪਲੇਟ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ:',
@@ -488,6 +703,10 @@ $1 ਘੰਟੇ ਵਿੱਚ। ਨੁਕਸਾਨ ਤੋਂ ਬਚਣ ਲਈ, $
 'template-semiprotected' => '(ਨੀਮ-ਸੁਰੱਖਿਅਤ)',
 'hiddencategories' => 'ਇਹ ਸਫ਼ਾ {{PLURAL:$1|1 ਲੁਕਵੀਂ ਸ਼੍ਰੇਣੀ|
 $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ ਹੈ:',
+'nocreatetitle' => 'ਸਫ਼ਾ ਬਣਾਉਣ ਦੀ ਹੱਦ ਹੈ',
+'nocreatetext' => '{{SITENAME}} ਨੇ ਨਵੇਂ ਸਫ਼ੇ ਬਣਾਉਣ ਤੇ ਰੋਕ ਲਾਈ ਹੋਈ ਹੈ।
+ਤੁਸੀਂ ਵਾਪਸ ਜਾ ਕੇ ਮੌਜੂਦਾ ਸਫ਼ੇ ਸੋਧ ਸਕਦੇ ਹੋ ਜਾਂ [[Special:UserLogin|ਲਾਗਇਨ ਜਾਂ ਖਾਤਾ ਬਣਾ]] ਸਕਦੇ ਹੋ।',
+'nocreate-loggedin' => 'ਤੁਹਾਨੂੰ ਨਵੇਂ ਸਫ਼ੇ ਬਣਾਉਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।',
 'permissionserrors' => 'ਅਧਿਕਾਰ ਗਲਤੀਆਂ',
 'permissionserrorstext' => 'ਤੁਹਾਨੂੰ ਇੰਝ ਕਰਨ ਦੇ ਅਧਿਕਾਰ ਨਹੀਂ ਹਨ। ਹੇਠ ਦਿੱਤੇ {{PLURAL:$1|ਕਾਰਨ|ਕਾਰਨ}} ਨੇ:',
 'permissionserrorstext-withaction' => '{{PLURAL:$1|ਇਸ ਕਾਰਨ|ਇਹਨਾਂ ਕਾਰਨਾਂ}} ਕਰਕੇ ਤੁਹਾਨੂੰ $2 ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ:',
@@ -498,6 +717,12 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 ਇਸਨੂੰ ਮਿਟਾਉਣ ਜਾਂ ਸਿਰਲੇਖ ਬਦਲੀ ਦਾ ਚਿੱਠਾ ਹੇਠਾਂ ਦਿੱਤਾ ਗਿਆ ਹੈ।",
 'moveddeleted-notice' => 'ਇਹ ਸਫ਼ਾ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ ਹੈ।
 ਇਸਦੇ ਮਿਟਾਉਣ ਜਾਂ ਸਿਰਲੇਖ ਬਦਲੀ ਦਾ ਚਿੱਠਾ ਹਵਾਲੇ ਲਈ ਹੇਠ ਦਿੱਤਾ ਗਿਆ ਹੈ।',
+'log-fulllog' => 'ਪੂਰਾ ਚਿੱਠਾ ਵੇਖੋ',
+'edit-gone-missing' => 'ਸਫ਼ਾ ਅਪਡੇਟ ਨਹੀਂ ਹੋ ਸਕਿਆ।
+ਲਗਦਾ ਹੈ ਮਿਟਾਇਆ ਜਾ ਚੁੱਕਾ ਹੈ।',
+'edit-no-change' => 'ਤੁਹਾਡੀ ਸੋਧ ਨਜ਼ਰਅੰਦਾਜ਼ ਕਰ ਦਿੱਤੀ ਗਈ ਹੈ ਕਿਉਂਕਿ ਲਿਖਤ ਵਿਚ ਕੋਈ ਤਬਦੀਲੀ ਨਹੀਂ ਕੀਤੀ ਗਈ।',
+'edit-already-exists' => 'ਨਵਾਂ ਸਫ਼ਾ ਨਹੀਂ ਬਣਾਇਆ ਜਾ ਸਕਿਆ।
+ਇਹ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।',
 
 # Parser/template warnings
 'post-expand-template-inclusion-warning' => "'''ਖ਼ਬਰਦਾਰ:''' ਟੈਂਪਲੇਟਾਂ ਦਾ ਅਕਾਰ ਬਹੁਤ ਵੱਡਾ ਹੈ। ਕੁਝ ਟੈਂਪਲੇਟ ਸ਼ਾਮਲ ਨਹੀਂ ਹੋਣਗੇ।",
@@ -505,12 +730,23 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'post-expand-template-argument-warning' => "'''ਖ਼ਬਰਦਾਰ:'''
 ਇਸ ਸਫ਼ੇ ਤੇ ਘੱਟੋ ਘੱਟ ਇਕ ਐਸੀ ਟੈਂਪਲੇਟ ਬਹਿਸ ਹੈ ਜਿਸ ਦਾ ਅਕਾਰ ਬਹੁਤ ਵੱਡਾ ਹੈ। ਐਸੀਆਂ ਬਹਿਸਾਂ ਨੂੰ ਛੱਡ ਦਿੱਤਾ ਗਿਆ ਹੈ।",
 'post-expand-template-argument-category' => 'ਐਸੇ ਸਫ਼ੇ ਜਿਨ੍ਹਾਂ ਵਿਚ ਫ਼ਰਮੇ ਦੇ ਸਁਘਟਕ ਛੁੱਟ ਗਏ ਹਨ ।',
+'parser-template-loop-warning' => 'ਸਾਂਚੇ ਦਾ ਲੂਪ ਲੱਭਿਆ: [[$1]]',
+
+# "Undo" feature
+'undo-success' => 'ਇਹ ਸੋਧ ਨਕਾਰੀ ਜਾ ਸਕਦੀ ਹੈ।
+ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਇਹ ਤਸਦੀਕ ਕਰਨ ਲਈ ਹੇਠਲੀ ਤੁਲਨਾ ਜਾਂਚੋ ਕਿ ਇਹ ਓਹੀ ਹੈ ਜੋ ਤੁਸੀਂ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ ਅਤੇ ਫਿਰ ਸੋਧ ਨਕਾਰਨ ਲਈ ਤਬਦੀਲੀਆਂ ਸਾਂਭ ਦਿਓ।',
+'undo-norev' => 'ਸੋਧ ਨਕਾਰੀ ਨਹੀਂ ਜਾ ਸਕਦੀ ਕਿਉਂਕਿ ਇਹ ਮੌਜੂਦ ਨਹੀਂ ਜਾਂ ਮਿਟਾ ਦਿੱਤੀ ਗਈ ਹੈ।',
+'undo-summary' => '[[Special:Contributions/$2|$2]] ([[User talk:$2|ਗੱਲ-ਬਾਤ]]) ਦੀ ਸੋਧ $1 ਨਕਾਰੀ',
 
 # Account creation failure
 'cantcreateaccounttitle' => 'ਅਕਾਊਂਟ ਬਣਾਇਆ ਨਹੀਂ ਜਾ ਸਕਦਾ',
+'cantcreateaccount-text' => "[[User:$3|$3]] ਨੇ ਇਸ IP ਪਤੇ ('''$1''') ਤੋਂ ਖਾਤਾ ਬਣਾਉਣ ਤੇ ਪਾਬੰਦੀ ਲਾਈ ਹੈ।
+
+$3 ਨੇ ਕਾਰਨ ਇਹ ਦੱਸਿਆ ਹੈ, ''$2''",
 
 # History pages
 'viewpagelogs' => 'ਇਸ ਸਫ਼ੇ ਲਈ ਚਿੱਠੇ ਵੇਖੋ',
+'nohistory' => 'ਇਸ ਸਫ਼ੇ ਦਾ ਕੋਈ ਸੋਧ ਅਤੀਤ ਨਹੀਂ ਹੈ।',
 'currentrev' => 'ਮੌਜੂਦਾ ਰੀਵਿਜ਼ਨ',
 'currentrev-asof' => '$1 ਮੁਤਾਬਕ ਸਭ ਤੋਂ ਨਵਾਂ ਰੀਵਿਜ਼ਨ',
 'revisionasof' => '$1 ਦਾ ਰੀਵਿਜ਼ਨ',
@@ -536,34 +772,97 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 
 # Revision feed
 'history-feed-title' => 'ਰੀਵਿਜ਼ਨ ਅਤੀਤ',
+'history-feed-description' => 'ਵਿਕੀ ਤੇ ਇਸ ਸਫ਼ੇ ਦਾ ਰੀਵਿਜ਼ਨ ਅਤੀਤ',
 'history-feed-item-nocomment' => '$1 ਤੋਂ $2 ’ਤੇ',
+'history-feed-empty' => 'ਦਰਖ਼ਾਸਤਸ਼ੁਦਾ ਸਫ਼ਾ ਮੌਜੂਦ ਨਹੀਂ ਹੈ।
+ਸ਼ਾਇਦ ਇਸਨੂੰ ਵਿਕੀ ਤੋਂ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ ਹੈ ਜਾਂ ਨਾਮ ਬਦਲ ਦਿੱਤਾ ਗਿਆ ਹੈ।
+ਵਿਕੀ ਦੇ ਨਵੇਂ ਮੁਨਾਸਿਬ ਸਫ਼ਿਆਂ ਵਿਚ [[Special:Search|ਲੱਭਣ]] ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ।',
 
 # Revision deletion
 'rev-deleted-comment' => '(ਟਿੱਪਣੀ ਹਟਾਈ)',
 'rev-deleted-user' => '(ਯੂਜ਼ਰ ਨਾਂ ਹਟਾਇਆ)',
 'rev-deleted-event' => '(ਐਂਟਰੀ ਹਟਾਈ)',
+'rev-deleted-user-contribs' => '[ਮੈਂਬਰ-ਨਾਂ ਜਾਂ IP ਪਤਾ ਹਟਾਇਆ - ਸੋਧ ਯੋਗਦਾਨਾਂ ਵਿਚੋਂ ਓਹਲੇ ਕੀਤੀ]',
+'rev-deleted-text-permission' => "ਸਫ਼ੇ ਦੀ ਇਹ ਰੀਵਿਜ਼ਨ '''ਮਿਟਾਈ''' ਜਾ ਚੁੱਕੀ ਹੈ।
+ਤਫ਼ਸੀਲ [{{fullurl:{{#Special:Log}}/delete|
+page={{FULLPAGENAMEE}}}} ਮਿਟਾਉਣ ਦੇ ਚਿੱਠੇ] ਵਿਚ ਵੇਖੀ ਜਾ ਸਕਦੀ ਹੈ।",
+'rev-deleted-text-unhide' => "ਸਫ਼ੇ ਦੀ ਇਹ ਰੀਵਿਜ਼ਨ '''ਮਿਟਾਈ''' ਜਾ ਚੁੱਕੀ ਹੈ।
+ਤਫ਼ਸੀਲ [{{fullurl:{{#Special:Log}}/delete|
+page={{FULLPAGENAMEE}}}} ਮਿਟਾਉਣ ਦੇ ਚਿੱਠੇ] ਵਿਚ ਵੇਖੀ ਜਾ ਸਕਦੀ ਹੈ।
+ਜੇ ਤੁਸੀਂ ਅੱਗੇ ਵਧਣਾ ਚਾਹੋ ਤਾਂ ਹਾਲੇ ਵੀ [$1 ਇਹ ਰੀਵਿਜ਼ਨ ਵੇਖ] ਸਕਦੇ ਹੋ।",
+'rev-deleted-no-diff' => "ਤੁਸੀਂ ਇਹ ਫ਼ਰਕ ਨਹੀਂ ਵੇਖ ਸਕਦੇ ਕਿਉਂਕਿ ਇਹਨਾਂ ਵਿੱਚੋਂ ਇੱਕ ਰੀਵਿਜ਼ਨ '''ਮਿਟਾਈ''' ਜਾ ਚੁੱਕੀ ਹੈ।
+ਤਫ਼ਸੀਲ [{{fullurl:{{#Special:Log}}/delete|
+page={{FULLPAGENAMEE}}}} ਮਿਟਾਉਣ ਦੇ ਚਿੱਠੇ] ਵਿਚ ਵੇਖੀ ਜਾ ਸਕਦੀ ਹੈ।",
+'rev-suppressed-no-diff' => "ਤੁਸੀਂ ਇਹ ਫ਼ਰਕ ਨਹੀਂ ਵੇਖ ਸਕਦੇ ਕਿਉਂਕਿ ਇਹਨਾਂ ਵਿੱਚੋਂ ਇੱਕ ਰੀਵਿਜ਼ਨ '''ਮਿਟਾਈ''' ਜਾ ਚੁੱਕੀ ਹੈ।",
+'rev-deleted-unhide-diff' => "ਇਸ ਫ਼ਰਕ ਵਿੱਚੋਂ ਇੱਕ ਰੀਵਿਜ਼ਨ '''ਮਿਟਾਈ''' ਜਾ ਚੁੱਕੀ ਹੈ।
+ਤਫ਼ਸੀਲ [{{fullurl:{{#Special:Log}}/delete|
+page={{FULLPAGENAMEE}}}} ਮਿਟਾਉਣ ਦੇ ਚਿੱਠੇ] ਵਿਚ ਵੇਖੀ ਜਾ ਸਕਦੀ ਹੈ।
+ਜੇ ਤੁਸੀਂ ਅੱਗੇ ਵਧਣਾ ਚਾਹੋ ਤਾਂ ਹਾਲੇ ਵੀ [$1 ਇਹ ਰੀਵਿਜ਼ਨ ਵੇਖ] ਸਕਦੇ ਹੋ।",
+'rev-suppressed-diff-view' => "ਇਸ ਫ਼ਰਕ ਵਿੱਚੋਂ ਇੱਕ ਰੀਵਿਜ਼ਨ '''ਜ਼ਬਤ''' ਕੀਤੀ ਜਾ ਚੁੱਕੀ ਹੈ।
+ਤਫ਼ਸੀਲ [{{fullurl:{{#Special:Log}}/delete|
+page={{FULLPAGENAMEE}}}} ਜ਼ਬਤੀ ਦੇ ਚਿੱਠੇ] ਵਿਚ ਵੇਖੀ ਜਾ ਸਕਦੀ ਹੈ।",
 'rev-delundel' => 'ਦਿਖਾਓ/ਲੁਕਾਓ',
+'rev-showdeleted' => 'ਵਖਾਓ',
+'revisiondelete' => 'ਰੀਵਿਜ਼ਨ ਮਿਟਾਓ/ਮਿਟਾਈ ਰੱਦ ਕਰੋ',
 'revdelete-nooldid-title' => 'ਕੋਈ ਟਾਰਗੇਟ ਰੀਵਿਜ਼ਨ ਨਹੀਂ',
+'revdelete-nologtype-title' => 'ਚਿੱਠੇ ਦੀ ਕਿਸਮ ਨਹੀਂ ਦੱਸੀ ਗਈ',
+'revdelete-nologtype-text' => 'ਇਹ ਕਾਰਵਾਈ ਕਰਨ ਲਈ ਤੁਸੀਂ ਚਿੱਠੇ ਦੀ ਕਿਸਮ ਨਹੀਂ ਦੱਸੀ।',
+'revdelete-no-file' => 'ਦੱਸੀ ਗਈ ਫ਼ਾਈਲ ਮੌਜੂਦ ਨਹੀਂ ਹੈ।',
+'revdelete-show-file-confirm' => 'ਤੁਹਾਨੂੰ ਯਕੀਨ ਹੈ ਤੁਸੀਂ $2 ਨੂੰ $3 ਦੀ ਫ਼ਾਈਲ "<nowiki>$1</nowiki>" ਦੀ ਮਿਟਾਈ ਗਈ ਰੀਵਿਜ਼ਨ ਵੇਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?',
+'revdelete-show-file-submit' => 'ਹਾਂ',
+'revdelete-selected' => "'''[[:$1]] {{PLURAL:$2|ਦੀ ਚੁਣੀ ਹੋਈ ਰੀਵਿਜ਼ਨ|ਦੀਆਂ ਚੁਣੀਆਂ ਹੋਈਆਂ ਰੀਵਿਜ਼ਨਾਂ}}:'''",
 'revdelete-legend' => 'ਪਾਬੰਦੀਆਂ ਸੈੱਟ ਕਰੋ:',
 'revdelete-hide-text' => 'ਰੀਵਿਜ਼ਨ ਟੈਕਸਟ ਓਹਲੇ',
 'revdelete-hide-image' => 'ਫਾਇਲ ਸਮੱਗਰੀ ਓਹਲੇ',
 'revdelete-hide-name' => 'ਐਕਸ਼ਨ ਅਤੇ ਟਾਰਗੇਟ ਓਹਲੇ',
+'revdelete-hide-comment' => 'ਸੋਧ ਸਾਰ ਲੁਕਾਓ',
+'revdelete-hide-user' => 'ਸੋਧਣ ਵਾਲ਼ੇ ਦਾ ਮੈਂਬਰ-ਨਾਂ/IP ਪਤਾ ਲੁਕਾਓ',
+'revdelete-radio-same' => '(ਨਹੀਂ ਬਦਲਣਾ)',
 'revdelete-radio-set' => 'ਹਾਂ',
+'revdelete-radio-unset' => 'ਨਹੀਂ',
+'revdelete-unsuppress' => 'ਮੁੜ ਬਹਾਲ ਕੀਤੀਆਂ ਰੀਵਿਜ਼ਨਾਂ ਤੋਂ ਰੋਕਾਂ ਹਟਾਓ',
 'revdelete-log' => 'ਕਾਰਨ:',
 'revdelete-submit' => 'ਚੁਣੇ ਰੀਵਿਜ਼ਨ ਉੱਤੇ ਲਾਗੂ ਕਰੋ',
+'logdelete-success' => "'''ਚਿੱਠੇ ਦੀ ਦਿੱਖ ਕਾਮਯਾਬੀ ਨਾਲ਼ ਸੈੱਟ ਕੀਤੀ।'''",
+'logdelete-failure' => "'''ਚਿੱਠੇ ਦੀ ਦਿੱਖ ਸੈੱਟ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ:''' $1",
 'revdel-restore' => 'ਦਿੱਖ ਬਦਲੋ',
 'revdel-restore-deleted' => 'ਮਿਟਾਏ ਗਏ ਰੀਵੀਜ਼ਨ',
 'revdel-restore-visible' => 'ਦਿੱਸਣਯੋਗ ਰੀਵੀਜ਼ਨ',
 'pagehist' => 'ਪੇਜ ਦਾ ਅਤੀਤ',
 'deletedhist' => 'ਹਟਾਇਆ ਗਿਆ ਅਤੀਤ',
+'revdelete-hide-current' => 'ਤਾਰੀਖ਼ &2, $1 ਦੀ ਚੀਜ਼ ਲੁਕਾਉਣ ਵਿਚ ਗ਼ਲਤੀ: ਇਹ ਮੌਜੂਦਾ ਰੀਵਿਜ਼ਨ ਹੈ।
+ਇਹ ਲੁਕਾਈ ਨਹੀਂ ਜਾ ਸਕਦੀ।',
+'revdelete-otherreason' => 'ਹੋਰ/ਵਾਧੂ ਕਾਰਨ:',
+'revdelete-reasonotherlist' => 'ਹੋਰ ਕਾਰਨ',
+'revdelete-edit-reasonlist' => 'ਮਿਟਾਏ ਜਾਣ ਦੇ ਕਾਰਨ ਸੋਧੋ',
+'revdelete-offender' => 'ਰੀਵਿਜ਼ਨ ਲੇਖਕ:',
+
+# History merging
+'mergehistory' => 'ਸਫ਼ਿਆਂ ਦੇ ਅਤੀਤ ਰਲ਼ਾਓ',
+'mergehistory-from' => 'ਸਰੋਤ ਸਫ਼ਾ:',
+'mergehistory-list' => 'ਰਲ਼ਾਉਣਯੋਗ ਸੋਧ ਅਤੀਤ',
+'mergehistory-go' => 'ਰਲ਼ਾਉਣਯੋਗ ਸੋਧਾਂ ਵਖਾਓ',
+'mergehistory-empty' => 'ਕੋਈ ਰੀਵਿਜ਼ਨ ਰਲ਼ਾਈ ਨਹੀ ਜਾ ਸਕਦੀ।',
+'mergehistory-no-source' => 'ਸਰੋਤ ਸਫ਼ਾ $1 ਮੌਜੂਦ ਨਹੀਂ ਹੈ।',
+'mergehistory-autocomment' => '[[:$1]] ਨੂੰ [[:$2]] ਵਿੱਚ ਰਲ਼ਾਇਆ',
+'mergehistory-comment' => '[[:$1]] ਨੂੰ [[:$2]] ਵਿੱਚ ਰਲ਼ਾਇਆ: $3',
+'mergehistory-same-destination' => 'ਸਰੋਤ ਸਫ਼ਾ ਅਤੇ ਮੰਜ਼ਿਲ ਸਫ਼ਾ ਇੱਕੋ ਜਿਹੇ ਨਹੀਂ ਹੋ ਸਕਦੇ',
+'mergehistory-reason' => 'ਕਾਰਨ:',
 
 # Merge log
+'mergelog' => 'ਰਲ਼ਾਉਣ ਦਾ ਚਿੱਠਾ',
+'pagemerge-logentry' => '[[$1]] ਨੂੰ [[$2]] ਵਿੱਚ ਰਲ਼ਾਇਆ ($3 ਤੱਕ ਦੀਆ ਰੀਵਿਜ਼ਨਾਂ)',
 'revertmerge' => 'ਅਨ-ਮਰਜ',
+'mergelogpagetext' => 'ਹੇਠਾਂ ਇੱਕ ਸਫ਼ੇ ਦੇ ਅਤੀਤ ਨੂੰ ਦੂਜੇ ਦੇ ਅਤੀਤ ਵਿਚ ਰਲ਼ਾਉਣ ਦੀ ਸਭ ਤੋਂ ਤਾਜ਼ਾ ਲਿਸਟ ਹੈ।',
 
 # Diffs
 'history-title' => '"$1" ਦੇ ਰੀਵਿਜ਼ਨ ਦਾ ਅਤੀਤ',
+'difference-title' => '"$1" ਦੇ ਰੀਵਿਜ਼ਨਾਂ ਵਿਚ ਫ਼ਰਕ',
+'difference-title-multipage' => 'ਸਫ਼ਿਆਂ "$1" ਅਤੇ "$2" ਵਿਚ ਫ਼ਰਕ',
+'difference-multipage' => '(ਦੋ ਸਫ਼ਿਆਂ ਵਿਚਕਾਰ ਫ਼ਰਕ)',
 'lineno' => 'ਲਾਈਨ $1:',
 'compareselectedversions' => 'ਚੁਣੇ ਵਰਜਨਾਂ ਦੀ ਤੁਲਨਾ',
+'showhideselectedversions' => 'ਚੁਣੇ ਰੀਵਿਜ਼ਨ ਵਖਾਓ/ਲੁਕਾਓ',
 'editundo' => 'ਨਕਾਰੋ',
 'diff-multi' => '({{PLURAL:$2|ਮੈਂਬਰ ਦੀ|$2 ਮੈਂਬਰਾਂ ਦੀਆਂ}} {{PLURAL:$1|ਵਿਚਕਾਰਲੀ ਰੀਵਿਜ਼ਨ ਨਹੀਂ ਦਿਖਾਈ ਜਾ ਰਹੀ|ਵਿਚਕਾਰਲੀਆਂ $1 ਰੀਵਿਜ਼ਨਾਂ ਨਹੀਂ ਦਿਖਾਈਆਂ ਜਾ ਰਹੀਆਂ}})',
 
@@ -583,9 +882,11 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'nextn-title' => 'ਅਗਲੇ $1 {{PLURAL:$1|ਨਤੀਜਾ|ਨਤੀਜੇ}}',
 'shown-title' => 'ਪ੍ਰਤੀ ਸਫ਼ਾ $1 {{PLURAL:$1|ਨਤੀਜਾ|ਨਤੀਜੇ}} ਵਖਾਓ',
 'viewprevnext' => 'ਵੇਖੋ ($1 {{int:pipe-separator}} $2) ($3)',
+'searchmenu-legend' => 'ਖੋਜ ਇਖ਼ਤਿਆਰ',
 'searchmenu-exists' => "'''ਇਸ ਵਿਕੀ ’ਤੇ \"[[:\$1]]\" ਨਾਮ ਦਾ ਸਫ਼ਾ ਹੈ।'''",
 'searchmenu-new' => "'''ਇਸ ਵਿਕੀ ’ਤੇ \"[[:\$1]]\" ਸਫ਼ਾ ਬਣਾਓ!'''",
 'searchhelp-url' => 'Help:ਸਮੱਗਰੀ',
+'searchmenu-prefix' => '[[Special:PrefixIndex/$1|ਇਸ ਅਗੇਤਰ ਵਾਲ਼ੇ ਸਫ਼ੇ ਵੇਖੋ]]',
 'searchprofile-articles' => 'ਸਮੱਗਰੀ ਸਫ਼ੇ',
 'searchprofile-project' => 'ਮਦਦ ਅਤੇ ਪ੍ਰੋਜੈਕਟ ਸਫ਼ੇ',
 'searchprofile-images' => 'ਮਲਟੀਮੀਡਿਆ',
@@ -600,12 +901,20 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'search-redirect' => '($1 ਰੀ-ਡਿਰੈਕਟ)',
 'search-section' => '(ਭਾਗ $1)',
 'search-suggest' => 'ਕੀ ਤੁਹਾਡਾ ਮਤਲਬ ਸੀ: $1',
+'search-interwiki-caption' => 'ਸਾਥੀ ਪ੍ਰੋਜੈਕਟ',
 'search-interwiki-default' => '$1 ਨਤੀਜੇ:',
 'search-interwiki-more' => '(ਹੋਰ)',
 'search-mwsuggest-enabled' => 'ਸੁਝਾਆਵਾਂ ਨਾਲ',
 'search-mwsuggest-disabled' => 'ਕੋਈ ਸੁਝਾਅ ਨਹੀਂ',
+'search-relatedarticle' => 'ਸਬੰਧਿਤ',
+'mwsuggest-disable' => 'AJAX ਸਲਾਹਾਂ ਬੰਦ ਕਰੋ',
+'searcheverything-enable' => 'ਸਾਰੇ ਥਾਂ-ਨਾਂਵਾਂ ਵਿਚ ਖੋਜੋ',
 'searchrelated' => 'ਸਬੰਧਿਤ',
 'searchall' => 'ਸਭ',
+'showingresults' => "ਹੇਠਾਂ #'''$2''' ਨਾਲ਼ ਸ਼ੁਰੂ ਹੋਣ ਵਾਲ਼ੇ {{PLURAL:
+$1|'''1''' ਨਤੀਜਾ|'''$1''' ਤੱਕ ਨਤੀਜੇ}} ਵਖਾਓ।",
+'showingresultsnum' => "ਹੇਠਾਂ #'''$2''' ਨਾਲ਼ ਸ਼ੁਰੂ ਹੋਣ ਵਾਲ਼ੇ {{PLURAL:
+$3|'''1''' ਨਤੀਜਾ|'''$3''' ਨਤੀਜੇ}} ਵਖਾਓ।",
 'showingresultsheader' => "'''$4''' ਵਾਸਤੇ {{PLURAL:$5|'''$3''' ਵਿਚੋਂ '''$1''' ਨਤੀਜੇ|'''$3''' ਵਿਚੋਂ '''$1 - $2''' ਨਤੀਜੇ}}",
 'search-nonefound' => 'ਤੁਹਾਡੀ ਖੋਜ ਨਾਲ਼ ਮੇਲ ਖਾਂਦੇ ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਮਿਲੇ।',
 'powersearch' => 'ਖੋਜ',
@@ -613,6 +922,10 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'powersearch-ns' => 'ਨੇਮ-ਸਪੇਸ ਵਿੱਚ ਖੋਜ:',
 'powersearch-redir' => 'ਰੀ-ਡਿਰੈਕਟ ਲਿਸਟ',
 'powersearch-field' => 'ਇਸ ਲਈ ਖੋਜ',
+'powersearch-togglelabel' => 'ਜਾਂਚੋ:',
+'powersearch-toggleall' => 'ਸਭ',
+'powersearch-togglenone' => 'ਕੋਈ ਨਹੀਂ',
+'search-external' => 'ਬਾਹਰੀ ਖੋਜ',
 
 # Quickbar
 'qbsettings' => 'ਤੁਰੰਤ ਬਾਰ',
@@ -629,48 +942,90 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'skin-preview' => 'ਝਲਕ',
 'datedefault' => 'ਕੋਈ ਪਸੰਦ ਨਹੀਂ',
 'prefs-datetime' => 'ਮਿਤੀ ਅਤੇ ਸਮਾਂ',
+'prefs-user-pages' => 'ਮੈਂਬਰ ਸਫ਼ੇ',
 'prefs-personal' => 'ਯੂਜ਼ਰ ਪਰੋਫਾਇਲ',
 'prefs-rc' => 'ਤਾਜ਼ਾ ਬਦਲਾਅ',
 'prefs-watchlist' => 'ਵਾਚ-ਲਿਸਟ',
+'prefs-watchlist-days' => 'ਨਿਗਰਾਨੀ-ਲਿਸਟ ਵਿਚ ਦਿਖਾਉਣ ਲਈ ਦਿਨ:',
+'prefs-watchlist-days-max' => 'ਵੱਧ ਤੋਂ ਵੱਧ $1 {{PLURAL:$1|ਦਿਨ|ਦਿਨ}}',
+'prefs-watchlist-edits' => 'ਵਧਾਈ ਹੋਈ ਨਿਗਰਾਨੀ-ਲਿਸਟ ਵਿਚ ਦਿਖਾਉਣ ਲਈ ਵੱਧ ਤੋਂ ਵੱਧ ਤਬਦੀਲੀਆਂ:',
+'prefs-watchlist-edits-max' => 'ਵੱਧ ਤੋਂ ਵੱਧ ਨੰਬਰ: ੧੦੦੦',
+'prefs-watchlist-token' => 'ਨਿਗਰਾਨੀ-ਲਿਸਟ ਟੋਕਨ:',
 'prefs-misc' => 'ਫੁਟਕਲ',
 'prefs-resetpass' => 'ਪਾਸਵਰਡ ਬਦਲੋ',
+'prefs-changeemail' => 'ਈ-ਮੇਲ ਪਤਾ ਬਦਲੋ',
+'prefs-setemail' => 'ਈ-ਮੇਲ ਪਤਾ ਸੈੱਟ ਕਰੋ',
 'prefs-email' => 'ਈਮੇਲ ਚੋਣਾਂ',
 'prefs-rendering' => 'ਦਿੱਖ',
 'saveprefs' => 'ਸੰਭਾਲੋ',
 'resetprefs' => 'ਰੀ-ਸੈੱਟ',
 'prefs-editing' => 'ਸੰਪਾਦਨ',
+'prefs-edit-boxsize' => 'ਸੋਧ ਖਿੜਕੀ ਦਾ ਅਕਾਰ',
 'rows' => 'ਕਤਾਰਾਂ:',
 'columns' => 'ਕਾਲਮ:',
 'searchresultshead' => 'ਖੋਜ',
 'resultsperpage' => 'ਪ੍ਰਤੀ ਪੇਜ ਹਿੱਟ:',
+'recentchangesdays' => 'ਤਾਜ਼ਾ ਤਬਦੀਲੀਆਂ ਵਿਚ ਦਿਖਾਉਣ ਲਈ ਦਿਨ:',
+'recentchangesdays-max' => 'ਵੱਧ ਤੋਂ ਵੱਧ $1 {{PLURAL:$1|ਦਿਨ|ਦਿਨ}}',
+'prefs-help-recentchangescount' => 'ਇਸ ਵਿਚ ਤਾਜ਼ਾ ਤਬਦੀਲੀਆਂ, ਸਫ਼ਿਆਂ ਦੇ ਅਤੀਤ ਅਤੇ ਚਿੱਠੇ ਸ਼ਾਮਲ ਹਨ।',
 'savedprefs' => 'ਤੁਹਾਡੀ ਪਸੰਦ ਸੰਭਾਲੀ ਗਈ ਹੈ।',
 'timezonelegend' => 'ਸਮਾਂ ਖੇਤਰ:',
 'localtime' => 'ਲੋਕਲ ਸਮਾਂ:',
 'timezoneuseserverdefault' => 'ਸਰਵਰ ਡਿਫਾਲਟ ਵਰਤੋਂ',
 'servertime' => 'ਸਰਵਰ ਟਾਈਮ',
 'guesstimezone' => 'ਬਰਾਊਜ਼ਰ ਤੋਂ ਭਰੋ',
+'timezoneregion-africa' => 'ਅਫ਼ਰੀਕਾ',
+'timezoneregion-america' => 'ਅਮਰੀਕਾ',
+'timezoneregion-antarctica' => 'ਅੰਟਾਰਕਟਿਕਾ',
+'timezoneregion-arctic' => 'ਆਰਕਟਿਕ',
+'timezoneregion-asia' => 'ਏਸ਼ੀਆ',
+'timezoneregion-atlantic' => 'ਅੰਧ ਮਹਾਂਸਾਗਰ',
+'timezoneregion-australia' => 'ਆਸਟ੍ਰੇਲੀਆ',
+'timezoneregion-europe' => 'ਯੂਰਪ',
+'timezoneregion-indian' => 'ਹਿੰਦ ਮਹਾਂਸਾਗਰ',
+'timezoneregion-pacific' => 'ਪ੍ਰਸ਼ਾਂਤ ਮਹਾਂਸਾਗਰ',
 'allowemail' => 'ਹੋਰ ਯੂਜ਼ਰਾਂ ਤੋਂ ਈਮੇਲ ਯੋਗ ਕਰੋ',
+'prefs-searchoptions' => 'ਖੋਜ ਇਖ਼ਤਿਆਰ',
+'prefs-namespaces' => 'ਥਾਂ-ਨਾਮ',
+'defaultns' => 'ਨਹੀਂ ਤਾਂ ਇਹਨਾਂ ਥਾਂ-ਨਾਂਵਾਂ ਵਿਚ ਖੋਜੋ:',
 'default' => 'ਡਿਫਾਲਟ',
 'prefs-files' => 'ਫਾਇਲਾਂ',
+'prefs-emailconfirm-label' => 'ਈ-ਮੇਲ ਤਸਦੀਕ:',
+'prefs-textboxsize' => 'ਸੋਧ ਖਿੜਕੀ ਦਾ ਅਕਾਰ',
 'youremail' => 'ਈ-ਮੇਲ:',
 'username' => 'ਯੂਜ਼ਰ ਨਾਂ:',
 'uid' => 'ਯੂਜ਼ਰ ID:',
 'yourrealname' => 'ਅਸਲੀ ਨਾਮ:',
 'yourlanguage' => 'ਭਾਸ਼ਾ:',
 'yournick' => 'ਛੋਟਾ ਨਾਂ:',
+'prefs-help-signature' => 'ਗੱਲ-ਬਾਤ ਸਫ਼ਿਆਂ ਉੱਤੇ ਟਿੱਪਣੀਆਂ ਦੇ ਆਖ਼ਰ ਵਿਚ "<nowiki>~~~~</nowiki>" ਲਾਓ ਜੋ ਤੁਹਾਡੇ ਦਸਤਖ਼ਤ ਅਤੇ ਵਕਤ ਦੀ ਮੋਹਰ ਵਿਚ ਤਬਦੀਲ ਹੋ ਜਾਵੇਗਾ।',
 'badsiglength' => 'ਛੋਟਾ ਨਾਂ (Nickname) ਬਹੁਤ ਲੰਮਾ ਹੋ ਗਿਆ ਹੈ, ਇਹ $1 ਅੱਖਰਾਂ ਤੋਂ ਘੱਟ ਚਾਹੀਦਾ ਹੈ।',
+'yourgender' => 'ਲਿੰਗ:',
+'gender-unknown' => 'ਜ਼ਾਹਿਰ ਨਹੀਂ ਕੀਤਾ',
+'gender-male' => 'ਮਰਦ',
+'gender-female' => 'ਔਰਤ',
 'email' => 'ਈਮੇਲ',
 'prefs-help-realname' => 'ਅਸਲੀ ਨਾਂ ਚੋਣਵਾਂ ਹੈ, ਅਤੇ ਜੇ ਤੁਸੀਂ ਇਹ ਦਿੱਤਾ ਹੈ ਤਾਂ ਤੁਹਾਡੇ ਕੰਮ ਵਾਸਤੇ ਗੁਣ ਦੇ ਤੌਰ ਉੱਤੇ ਵਰਤਿਆ ਜਾਵੇਗਾ।',
 'prefs-help-email' => 'ਤੁਹਾਡੀ ਮਰਜ਼ੀ ਹੈ ਈਮੇਲ ਪਤਾ ਦਿਓ ਜਾਂ ਨਾ ਦਿਓ ਪਰ ਪਾਸਵਰਡ ਭੁੱਲ ਜਾਣ ਤੇ ਨਵਾਂ ਪਾਸਵਰਡ ਹਾਸਲ ਕਰਨ ਲਈ ਇਹ ਜ਼ਰੂਰੀ ਹੈ।',
 'prefs-help-email-others' => 'ਤੁਸੀਂ ਇਹ ਵੀ ਚੁਣ ਸਕਦੇ ਹੋ ਕਿ ਤੁਹਾਡੇ ਮੈਂਬਰ ਜਾਂ ਗੱਲ-ਬਾਤ ਸਫ਼ੇ ਤੋਂ ਹੋਰ ਮੈਂਬਰ ਤੁਹਾਨੂੰ ਈ-ਮੇਲ ਭੇਜ ਸਕਣ?
 ਜਦੋਂ ਹੋਰ ਮੈਂਬਰ ਤੁਹਾਨੂੰ ਈ-ਮੇਲ ਭੇਜਦੇ ਹਨ ਤਾਂ ਤੁਹਾਡਾ ਈ-ਮੇਲ ਪਤਾ ਜ਼ਾਹਰ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ।',
+'prefs-help-email-required' => 'ਈ-ਮੇਲ ਪਤਾ ਚਾਹੀਦਾ ਹੈ।',
+'prefs-info' => 'ਮੁੱਢਲੀ ਜਾਣਕਾਰੀ',
+'prefs-signature' => 'ਦਸਤਖ਼ਤ',
+'prefs-dateformat' => 'ਤਾਰੀਖ਼ ਅੰਦਾਜ਼',
 'prefs-advancedediting' => 'ਤਕਨੀਕੀ ਚੋਣਾਂ',
 'prefs-advancedrc' => 'ਤਕਨੀਕੀ ਚੋਣਾਂ',
 'prefs-advancedrendering' => 'ਤਕਨੀਕੀ ਚੋਣਾਂ',
 'prefs-advancedsearchoptions' => 'ਤਕਨੀਕੀ ਚੋਣਾਂ',
 'prefs-advancedwatchlist' => 'ਤਕਨੀਕੀ ਚੋਣਾਂ',
+'prefs-diffs' => 'ਫ਼ਰਕ',
+
+# User preference: e-mail validation using jQuery
+'email-address-validity-valid' => 'ਈ-ਮੇਲ ਪਤਾ ਸਹੀ ਲਗਦਾ ਹੈ',
+'email-address-validity-invalid' => 'ਸਹੀ ਈ-ਮੇਲ ਪਤਾ ਦਾਖ਼ਲ ਕਰੋ',
 
 # User rights
+'userrights' => 'ਮੈਂਬਰ ਦੇ ਹੱਕਾਂ ਦਾ ਰੱਖ-ਰਖਾਓ',
 'userrights-lookup-user' => 'ਯੂਜ਼ਰ ਗਰੁੱਪ ਦੇਖਭਾਲ',
 'userrights-user-editname' => 'ਇੱਕ ਯੂਜ਼ਰ ਨਾਂ ਦਿਓ:',
 'editusergroup' => 'ਯੂਜ਼ਰ ਗਰੁੱਪ ਸੋਧ',
@@ -679,23 +1034,68 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'saveusergroups' => 'ਯੂਜ਼ਰ ਗਰੁੱਪ ਸੰਭਾਲੋ',
 'userrights-groupsmember' => 'ਇਸ ਦਾ ਮੈਂਬਰ:',
 'userrights-reason' => 'ਕਾਰਨ:',
+'userrights-no-interwiki' => 'ਤੁਹਾਨੂੰ ਦੂਜੇ ਵਿਕੀਆਂ ਤੇ ਮੈਂਬਰਾਂ ਦੇ ਹੱਕਾਂ ਵਿਚ ਤਬਦੀਲੀ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।',
+'userrights-nodatabase' => 'ਡੈਟਾਬੇਸ $1 ਮੌਜੂਦ ਨਹੀਂ ਜਾਂ ਮਕਾਮੀ ਨਹੀਂ ਹੈ।',
+'userrights-notallowed' => 'ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਮੈਂਬਰ ਨੂੰ ਹੱਕ ਦੇਣ ਜਾਂ ਖੋਹਣ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।',
 
 # Groups
 'group' => 'ਗਰੁੱਪ:',
 'group-user' => 'ਮੈਂਬਰ',
+'group-autoconfirmed' => 'ਖ਼ੁਦ-ਤਸਦੀਕਸ਼ੁਦਾ ਮੈਂਬਰ',
+'group-bot' => 'ਬੋਟ',
 'group-all' => '(ਸਭ)',
 
 'group-user-member' => 'ਮੈਂਬਰ',
 
 # Rights
+'right-read' => 'ਸਫ਼ੇ ਪੜ੍ਹਨਾ',
 'right-edit' => 'ਸਫ਼ੇ ਸੋਧ',
+'right-createpage' => 'ਸਫ਼ੇ ਬਣਾਉਣਾ (ਜੋ ਚਰਚਾ ਸਫ਼ੇ ਨਾ ਹੋਣ)',
+'right-createtalk' => 'ਚਰਚਾ ਸਫ਼ੇ ਬਣਾਉਣਾ',
+'right-minoredit' => 'ਸੋਧਾਂ ਦੇ ਛੋਟਾ ਹੋਣ ਲਈ ਨਿਸ਼ਾਨ ਲਾਉਣਾ',
+'right-move' => 'ਸਫ਼ੇ ਭੇਜਣਾ',
+'right-upload' => 'ਫ਼ਾਈਲਾਂ ਅੱਪਲੋਡ ਕਰਨਾ',
+'right-autoconfirmed' => 'ਨੀਮ-ਸੁਰੱਖਿਅਤ ਸਫ਼ਿਆਂ ਨੂੰ ਸੋਧਣਾ',
 'right-delete' => 'ਸਫ਼ੇ ਹਟਾਓ',
+'right-bigdelete' => 'ਵੱਡੇ ਅਤੀਤਾਂ ਵਾਲ਼ੇ ਸਫ਼ੇ ਮਿਟਾਉਣੇ',
+'right-browsearchive' => 'ਮਿਟਾਏ ਗਏ ਸਫ਼ੇ ਖੋਜਣਾ',
+'right-undelete' => 'ਸਫ਼ੇ ਨੂੰ ਅਣ-ਮਿਟਾਇਆ ਕਰਨਾ',
+'right-block' => 'ਦੂਜੇ ਮੈਂਬਰਾਂ ਦੇ ਸੋਧ ਕਰਨ ਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ',
+'right-blockemail' => 'ਮੈਂਬਰ ਦੇ ਈ-ਮੇਲ ਭੇਜਣ ਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ',
+'right-hideuser' => 'ਮੈਂਬਰ-ਨਾਂ ਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ ਅਤੇ ਇਸਨੂੰ ਲੋਕਾਂ ਤੋਂ ਲੁਕਾਉਣਾ',
+'right-unwatchedpages' => 'ਨਜ਼ਰ ਨਾ ਰੱਖੇ ਜਾ ਰਹੇ ਸਫ਼ਿਆਂ ਦੀ ਲਿਸਟ ਵੇਖਣੀ',
+'right-mergehistory' => 'ਸਫ਼ਿਆਂ ਦੇ ਅਤੀਤਾਂ ਨੂੰ ਰਲ਼ਾਉਣਾ',
+'right-userrights' => 'ਸਾਰੇ ਮੈਂਬਰ ਹੱਕਾਂ ਵਿਚ ਸੋਧ ਕਰਨਾ',
+'right-userrights-interwiki' => 'ਦੂਜੇ ਵਿਕੀਆਂ ਤੇ ਮੈਂਬਰਾਂ ਦੇ ਮੈਂਬਰ ਹੱਕਾਂ ਵਿਚ ਸੋਧ ਕਰਨਾ',
+'right-siteadmin' => 'ਡੈਟਾਬੇਸ ਨੂੰ ਤਾਲਾ ਲਾਉਣਾ ਤੇ ਖੋਲ੍ਹਣਾ',
+'right-sendemail' => 'ਦੂਜੇ ਮੈਂਬਰਾਂ ਨੂੰ ਈ-ਮੇਲ ਭੇਜਣਾ',
 
 # User rights log
+'rightslog' => 'ਮੈਂਬਰ ਹੱਕਾਂ ਦਾ ਚਿੱਠਾ',
+'rightslogtext' => 'ਇਹ ਮੈਂਬਰ ਹੱਕਾਂ ਵਿਚ ਹੋਈਆਂ ਤਬਦੀਲੀਆਂ ਦਾ ਚਿੱਠਾ ਹੈ।',
+'rightslogentry-autopromote' => '$2 ਤੋਂ ਆਪਣੇ ਆਪ $3 ਤੱਕ ਤਰੱਕੀ ਕਰੀ',
 'rightsnone' => '(ਕੋਈ ਨਹੀਂ)',
 
 # Associated actions - in the sentence "You do not have permission to X"
+'action-read' => 'ਇਹ ਸਫ਼ਾ ਪੜ੍ਹਨ',
 'action-edit' => 'ਇਹ ਸਫ਼ਾ ਸੋਧੋ',
+'action-createpage' => 'ਸਫ਼ੇ ਬਣਾਉਣ',
+'action-createtalk' => 'ਚਰਚਾ ਸਫ਼ੇ ਬਣਾਉਣ',
+'action-createaccount' => 'ਮੈਂਬਰ ਖਾਤਾ ਬਣਾਉਣ',
+'action-upload' => 'ਇਹ ਫ਼ਾਈਲ ਅੱਪਲੋਡ ਕਰਨ',
+'action-delete' => 'ਇਹ ਸਫ਼ਾ ਮਿਟਾਉਣ',
+'action-deleterevision' => 'ਇਹ ਰੀਵਿਜ਼ਨ ਮਿਟਾਉਣ',
+'action-deletedhistory' => 'ਇਸ ਸਫ਼ੇ ਦਾ ਮਿਟਾਇਆ ਅਤੀਤ ਵੇਖਣ',
+'action-browsearchive' => 'ਮਿਟਾਏ ਸਫ਼ੇ ਖੋਜਣ',
+'action-undelete' => 'ਇਹ ਸਫ਼ਾ ਅਣ-ਮਿਟਿਆ ਕਰਨ',
+'action-block' => 'ਇਸ ਮੈਂਬਰ ਦੇ ਸੋਧ ਕਰਨ ਤੇ ਪਾਬੰਦੀ ਲਾਉਣ',
+'action-protect' => 'ਇਸ ਸਫ਼ੇ ਦੀ ਸੁਰੱਖਿਆ ਬਦਲਣ',
+'action-unwatchedpages' => 'ਨਜ਼ਰ ਨਾ ਰੱਖੇ ਜਾ ਰਹੇ ਸਫ਼ਿਆਂ ਦੀ ਲਿਸਟ ਵੇਖਣ',
+'action-mergehistory' => 'ਇਸ ਸਫ਼ੇ ਦੇ ਅਤੀਤ ਨੂੰ ਰਲ਼ਾਉਣ',
+'action-userrights' => 'ਸਾਰੇ ਮੈਂਬਰ ਹੱਕ ਸੋਧਣ',
+'action-userrights-interwiki' => 'ਦੂਜੇ ਵਿਕੀਆਂ ਤੇ ਮੈਂਬਰਾਂ ਦੇ ਮੈਂਬਰ ਹੱਕ ਸੋਧਣ',
+'action-siteadmin' => 'ਡੈਟਾਬੇਸ ਨੂੰ ਤਾਲਾ ਲਾਉਣ ਜਾਂ ਖੋਲ੍ਹਣ',
+'action-sendemail' => 'ਈ-ਮੇਲਾਂ ਭੇਜਣ',
 
 # Recent changes
 'nchanges' => '$1 {{PLURAL:$1|ਤਬਦੀਲੀ|
@@ -747,6 +1147,8 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'uploadnologintext' => 'ਤੁਹਾਨੂੰ[[Special:UserLogin|logged in] ਕਰਨਾ ਪਵੇਗਾ]
 to upload files.',
 'uploaderror' => 'ਅੱਪਲੋਡ ਗਲਤੀ',
+'upload-recreate-warning' => "'''ਖ਼ਬਰਦਾਰ: ਇਸ ਨਾਮ ਦੀ ਫ਼ਾਈਲ ਮਿਟਾਈ ਜਾਂ ਹੋਰ ਨਾਮ ਤੇ ਭੇਜੀ ਜਾ ਚੁੱਕੀ ਹੈ।'''
+ਮਿਟਾਉਣ ਅਤੇ ਭੇਜੇ ਜਾਣ ਦਾ ਚਿੱਠਾ ਸਹੂਲਤ ਲਈ ਇੱਥੇ ਦਿੱਤਾ ਗਿਆ ਹੈ:",
 'uploadlog' => 'ਅੱਪਲੋਡ ਲਾਗ',
 'uploadlogpage' => 'ਅੱਪਲੋਡ ਦਾ ਚਿੱਠਾ',
 'filename' => 'ਫਾਇਲ ਨਾਂ',
@@ -756,20 +1158,28 @@ to upload files.',
 'filesource' => 'ਸੋਰਸ:',
 'uploadedfiles' => 'ਅੱਪਲੋਡ ਕੀਤੀਆਂ ਫਾਇਲਾਂ',
 'ignorewarning' => 'ਚੇਤਾਵਨੀ ਅਣਡਿੱਠੀ ਕਰਕੇ ਕਿਵੇਂ ਵੀ ਫਾਇਲ ਸੰਭਾਲੋ।',
+'ignorewarnings' => 'ਕੋਈ ਚੇਤਾਵਨੀ ਹੋਈ ਤਾਂ ਨਜ਼ਰਅੰਦਾਜ਼ ਕਰੋ',
 'minlength1' => 'ਫਾਇਲ ਨਾਂ ਵਿੱਚ ਘੱਟੋ-ਘੱਟ ਇੱਕ ਅੱਖਰ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।',
 'badfilename' => 'ਫਾਇਲ ਨਾਂ "$1" ਬਦਲਿਆ ਗਿਆ ਹੈ।',
 'filetype-missing' => 'ਫਾਇਲ ਦੀ ਕੋਈ ਐਕਸ਼ਟੇਸ਼ਨ ਨਹੀਂ ਹੈ (ਜਿਵੇਂ ".jpg").',
+'filename-tooshort' => 'ਫ਼ਾਈਲ ਬਹੁਤ ਛੋਟੀ ਹੈ।',
+'filetype-banned' => 'ਇਸ ਕਿਸਮ ਦੀ ਫ਼ਾਈਲ ਦੀ ਮਨਾਹੀ ਹੈ।',
+'illegal-filename' => 'ਇਸ ਫ਼ਾਈਲ-ਨਾਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।',
 'fileexists' => "ਇਹ ਫਾਇਲ ਨਾਂ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ। ਜੇ ਤੁਸੀਂ ਇਹ ਬਦਲਣ ਬਾਰੇ ਜਾਣਦੇ ਨਹੀਂ ਹੋ ਤਾਂ  '''<tt>[[:$1]]</tt>''' ਵੇਖੋ ਜੀ। [[$1|thumb]]",
 'fileexists-extension' => "ਇਸ ਨਾਂ ਨਾਲ ਰਲਦੀ ਫਾਇਲ ਮੌਜੂਦ ਹੈ: [[$2|thumb]]
 * ਅੱਪਲੋਡ ਕੀਤੀ ਫਾਇਲ ਦਾ ਨਾਂ: '''<tt>[[:$1]]</tt>'''
 * ਮੌਜੂਦ ਫਾਇਲ ਦਾ ਨਾਂ: '''<tt>[[:$2]]</tt>'''
 ਇੱਕ ਵੱਖਰਾ ਨਾਂ ਚੁਣੋ ਜੀ",
+'file-exists-duplicate' => 'ਇਹ ਫ਼ਾਈਲ {{PLURAL:$1|ਇਸ ਫ਼ਾਈਲ|ਇਹਨਾਂ ਫ਼ਾਈਲਾਂ}} ਦੀ ਨਕਲ ਹੈ:',
 'uploadwarning' => 'ਅੱਪਲੋਡ ਚੇਤਾਵਨੀ',
 'savefile' => 'ਫਾਇਲ ਸੰਭਾਲੋ',
 'uploadedimage' => '"[[$1]]" ਅੱਪਲੋਡ ਕੀਤੀ',
+'overwroteimage' => '"[[$1]]" ਦਾ ਨਵਾਂ ਰੂਪ ਅੱਪਲੋਡ ਕਰੋ',
 'uploaddisabled' => 'ਅੱਪਲੋਡ ਆਯੋਗ ਹੈ',
 'uploadvirus' => 'ਇਹ ਫਾਇਲ ਵਿੱਚ ਵਾਇਰਸ ਹੈ! ਵੇਰਵੇ ਲਈ ਵੇਖੋ: $1',
 'sourcefilename' => 'ਸੋਰਸ ਫਾਇਲ ਨਾਂ:',
+'upload-maxfilesize' => 'ਫ਼ਾਈਲ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਅਕਾਰ: $1',
+'upload-description' => 'ਫ਼ਾਈਲ ਦਾ ਵੇਰਵਾ',
 'watchthisupload' => 'ਇਸ ਫਾਇਲ ਨੂੰ ਵਾਚ ਕਰੋ',
 'upload-success-subj' => 'ਠੀਕ ਤਰ੍ਹਾਂ ਅੱਪਲੋਡ',
 'upload-warning-subj' => 'ਅੱਪਲੋਡ ਚੇਤਾਵਨੀ',
@@ -777,6 +1187,21 @@ to upload files.',
 'upload-file-error' => 'ਅੰਦਰੂਨੀ ਗਲਤੀ',
 'upload-misc-error' => 'ਅਣਜਾਣ ਅੱਪਲੋਡ ਗਲਤੀ',
 
+# File backend
+'backend-fail-notexists' => 'ਫ਼ਾਈਲ $1 ਮੌਜੂਦ ਨਹੀਂ ਹੈ।',
+'backend-fail-delete' => 'ਫ਼ਾਈਲ "$1" ਮਿਟਾਈ ਨਹੀਂ ਜਾ ਸਕੀ।',
+'backend-fail-alreadyexists' => 'ਫ਼ਾਈਲ "$1" ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।',
+'backend-fail-store' => 'ਫ਼ਾਈਲ "$1", "$2" ਵਿਚ ਸਾਂਭੀ ਨਹੀਂ ਜਾ ਸਕੀ।',
+'backend-fail-copy' => 'ਫ਼ਾਈਲ "$1", "$2" ਵਿਚ ਨਕਲ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ।',
+'backend-fail-move' => 'ਫ਼ਾਈਲ "$1", "$2" ਤੇ ਭੇਜੀ ਨਹੀਂ ਜਾ ਸਕੀ।',
+'backend-fail-opentemp' => 'ਆਰਜ਼ੀ ਫ਼ਾਈਲ ਖੋਲ੍ਹੀ ਨਹੀਂ ਜਾ ਸਕੀ।',
+
+# Special:UploadStash
+'uploadstash-refresh' => 'ਫ਼ਾਈਲਾਂ ਦੀ ਲਿਸਟ ਨੂੰ ਤਾਜ਼ਾ ਕਰੋ',
+
+# img_auth script messages
+'img-auth-nofile' => 'ਫ਼ਾਈਲ "$1" ਮੌਜੂਦ ਨਹੀਂ ਹੈ।',
+
 # Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
 'upload-curl-error28' => 'ਅੱਪਲੋਡ ਟਾਈਮ-ਆਉਟ',
 
@@ -789,6 +1214,7 @@ to upload files.',
 # Special:ListFiles
 'imgfile' => 'ਫਾਇਲ',
 'listfiles' => 'ਫਾਇਲ ਲਿਸਟ',
+'listfiles_thumb' => 'ਨਮੂਨਾ-ਤਸਵੀਰ',
 'listfiles_date' => 'ਮਿਤੀ',
 'listfiles_name' => 'ਨਾਂ',
 'listfiles_user' => 'ਯੂਜ਼ਰ',
@@ -807,6 +1233,7 @@ to upload files.',
 'filehist-datetime' => 'ਤਾਰੀਖ਼/ਸਮਾਂ',
 'filehist-thumb' => 'ਨਮੂਨਾ',
 'filehist-thumbtext' => '$1 ਦੇ ਸਮੇਂ ਦੇ ਸੰਸਕਰਨ ਦਾ ਅੰਗੂਠਾਕਾਰ ਪ੍ਰਤੀਰੂਪ',
+'filehist-nothumb' => 'ਕੋਈ ਨਮੂਨਾ-ਤਸਵੀਰ ਨਹੀਂ',
 'filehist-user' => 'ਮੈਂਬਰ',
 'filehist-dimensions' => 'ਨਾਪ',
 'filehist-filesize' => 'ਫਾਇਲ ਆਕਾਰ',
@@ -814,8 +1241,18 @@ to upload files.',
 'imagelinks' => 'ਫ਼ਾਈਲ ਦੀ ਵਰਤੋਂ',
 'linkstoimage' => 'ਇਹ {{PLURAL:$1|ਸਫ਼ੇ ਦੇ ਲਿੰਕ|$1 ਸਫ਼ੇ}} ਇਸ ਫ਼ਾਈਲ ਨਾਲ਼ ਜੋੜਦੇ ਹਨੇ:',
 'nolinkstoimage' => 'ਕੋਈ ਵੀ ਸਫ਼ਾ ਇਸ ਫ਼ਾਈਲ ਨਾਲ਼ ਨਹੀਂ ਜੋੜਦਾ।',
+'sharedupload' => 'ਇਹ ਫ਼ਾਈਲ $1 ਤੋਂ ਹੈ ਅਤੇ ਸ਼ਾਇਦ ਦੂਜੇ ਪ੍ਰੋਜੈਕਟਾਂ ਤੇ ਵਰਤੀ ਜਾ ਸਕਦੀ ਹੈ।',
+'sharedupload-desc-there' => 'ਇਹ ਫ਼ਾਈਲ $1 ਤੋਂ ਹੈ ਅਤੇ ਸ਼ਾਇਦ ਦੂਜੇ ਪ੍ਰੋਜੈਕਟਾਂ ਦੁਆਰਾ ਵਰਤੀ ਜਾ ਸਕਦੀ ਹੈ।
+ਜ਼ਿਆਦਾ ਜਾਣਕਾਰੀ ਲਈ ਮਿਹਰਬਾਨੀ ਕਰਕੇ [$2 ਫ਼ਾਈਲ ਦਾ ਵੇਰਵਾ ਸਫ਼ਾ] ਵੇਖੋ।',
 'sharedupload-desc-here' => 'ਇਹ ਫ਼ਾਈਲ $1 ਦੀ ਹੈ ਅਤੇ ਹੋਰ ਪ੍ਰਾਜੈਕਟਾਂ ਵਿਚ ਵੀ ਵਰਤੀ ਜਾ ਸਕਦੀ ਹੈ । ਇਸ [$2 ਫ਼ਾਈਲ ਦੇ ਵੇਰਵਾ ਸਫ਼ੇ] ਵਿਚ ਮੌਜੂਦ ਵੇਰਵਾ ਹੇਠ ਦਿਸ ਰਿਹਾ ਹੈ।',
+'sharedupload-desc-edit' => 'ਇਹ ਫ਼ਾਈਲ $1 ਤੋਂ ਹੈ ਅਤੇ ਸ਼ਾਇਦ ਦੂਜੇ ਪ੍ਰੋਜੈਕਟਾਂ ਦੁਆਰਾ ਵਰਤੀ ਜਾ ਸਕਦੀ ਹੈ।
+ਸ਼ਾਇਦ ਤੁਸੀਂ [$2 ਫ਼ਾਈਲ ਦੇ ਵੇਰਵੇ ਸਫ਼ੇ] ਤੇ ਇਸਦਾ ਵੇਰਵਾ ਬਦਲਣਾ ਚਾਹੋ।',
+'sharedupload-desc-create' => 'ਇਹ ਫ਼ਾਈਲ $1 ਤੋਂ ਹੈ ਅਤੇ ਸ਼ਾਇਦ ਦੂਜੇ ਪ੍ਰੋਜੈਕਟਾਂ ਦੁਆਰਾ ਵਰਤੀ ਜਾ ਸਕਦੀ ਹੈ।
+ਸ਼ਾਇਦ ਤੁਸੀਂ [$2 ਫ਼ਾਈਲ ਦੇ ਵੇਰਵੇ ਸਫ਼ੇ] ਤੇ ਇਸਦਾ ਵੇਰਵਾ ਬਦਲਣਾ ਚਾਹੋ।',
+'filepage-nofile' => 'ਇਸ ਨਾਮ ਦੀ ਕੋਈ ਫ਼ਾਈਲ ਮੌਜੂਦ ਨਹੀਂ ਹੈ।',
+'filepage-nofile-link' => 'ਇਸ ਨਾਮ ਦੀ ਕੋਈ ਫ਼ਾਈਲ ਮੌਜੂਦ ਨਹੀਂ ਹੈ ਪਰ ਤੁਸੀਂ [$1 ਇਸਨੂੰ ਅੱਪਲੋਡ ਕਰ] ਸਕਦੇ ਹੋ।',
 'uploadnewversion-linktext' => 'ਇਸ ਫਾਇਲ ਦਾ ਇੱਕ ਨਵਾਂ ਵਰਜਨ ਅੱਪਲੋਡ ਕਰੋ',
+'shared-repo-from' => '$1 ਤੋਂ',
 
 # File reversion
 'filerevert' => '$1 ਰੀਵਰਟ',
@@ -844,6 +1281,8 @@ to upload files.',
 'statistics-header-edits' => 'ਸੋਧ ਅੰਕੜੇ',
 'statistics-header-views' => 'ਵੇਖਣ ਅੰਕੜੇ',
 'statistics-header-users' => 'ਯੂਜ਼ਰ ਅੰਕੜੇ',
+'statistics-edits-average' => 'ਪ੍ਰਤੀ ਸਫ਼ਾ ਔਸਤਨ ਸੋਧਾਂ',
+'statistics-users-active' => 'ਚੁਸਤ ਮੈਂਬਰ',
 'statistics-mostpopular' => 'ਸਭ ਤੋਂ ਵੱਧ ਵੇਖੇ ਪੇਜ',
 
 'brokenredirects-edit' => 'ਸੋਧ',
index 6b787f4..6a8f71b 100644 (file)
@@ -7,6 +7,7 @@
  * @ingroup Language
  * @file
  *
+ * @author Ankry
  * @author Bartek50003
  * @author BdgwksxD
  * @author Beau
@@ -558,6 +559,10 @@ $1',
 'youhavenewmessages' => 'Masz $1 ($2).',
 'newmessageslink' => 'nowe wiadomości',
 'newmessagesdifflink' => 'różnica z poprzednią wersją',
+'youhavenewmessagesfromusers' => 'Masz $1 od {{PLURAL:$3|innego użytkownika|$3 innych użytkowników}} ($2).',
+'youhavenewmessagesmanyusers' => 'Masz $1 od wielu użytkowników ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|jedną wiadomość|$1 wiadomości}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|ostatnia zmiana|ostatnie $1 zmiany|ostatnie $1 zmian}}',
 'youhavenewmessagesmulti' => 'Masz nowe wiadomości na $1',
 'editsection' => 'edytuj',
 'editold' => 'edytuj',
@@ -707,6 +712,7 @@ Nie zapomnij dostosować [[Special:Preferences|preferencji dla {{GRAMMAR:D.lp|{{
 'remembermypassword' => 'Zapamiętaj moje hasło na tym komputerze (maksymalnie przez $1 {{PLURAL:$1|dzień|dni}})',
 'securelogin-stick-https' => 'Po zalogowaniu utrzymuj połączenie poprzez HTTPS',
 'yourdomainname' => 'Twoja domena',
+'password-change-forbidden' => 'Nie można zmieniać haseł na tej wiki.',
 'externaldberror' => 'Wystąpił błąd zewnętrznej bazy autentyfikacyjnej lub nie posiadasz uprawnień koniecznych do aktualizacji zewnętrznego konta.',
 'login' => 'Zaloguj się',
 'nav-login-createaccount' => 'Logowanie i rejestracja',
@@ -958,6 +964,9 @@ Możesz [[Special:Search/{{PAGENAME}}|poszukać „{{PAGENAME}}” na innych str
 'noarticletext-nopermission' => 'Na tej stronie nie ma jeszcze artykułu.
 Możesz [[Special:Search/{{PAGENAME}}|wyszukać ten tytuł]] w treści innych stron
 lub <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przeszukać powiązane logi].</span>',
+'missing-revision' => 'Wersja #$1 strony "{{PAGENAME}}" nie istnieje.
+
+Zazwyczaj jest to spowodowane przestarzałym linkiem do usuniętej strony. Powód usunięcia znajduje się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze].',
 'userpage-userdoesnotexist' => 'Użytkownik „<nowiki>$1</nowiki>” nie jest zarejestrowany.
 Upewnij się, czy na pewno zamierza{{GENDER:|łeś|łaś|sz}} utworzyć lub zmodyfikować właśnie tę stronę.',
 'userpage-userdoesnotexist-view' => 'Konto użytkownika „$1” nie jest zarejestrowane.',
@@ -1086,6 +1095,7 @@ Argument ten będzie pominięty.',
 'expansion-depth-exceeded-warning' => 'Strona przekroczyła głębokość rozbudowy',
 'parser-unstrip-loop-warning' => 'Wykryto nieskończoną pętlę',
 'parser-unstrip-recursion-limit' => 'Przekroczono maksymalną głębokość zagnieżdżania ($1)',
+'converter-manual-rule-error' => 'Błąd w językowych regułach konwersji',
 
 # "Undo" feature
 'undo-success' => 'Edycja może zostać wycofana. Porównaj ukazane poniżej różnice między wersjami, a następnie zapisz zmiany.',
@@ -1271,6 +1281,9 @@ Użycie linków nawigacyjnych kasuje wybór w kolumnie.',
 'editundo' => 'anuluj edycję',
 'diff-multi' => '(Nie pokazano $1 wersji {{PLURAL:$1|utworzonej|utworzonych}} przez {{PLURAL:$2|jednego użytkownika|$2 użytkowników}})',
 'diff-multi-manyusers' => '(Nie pokazano $1 {{PLURAL:$1|pośredniej wersji utworzonej|pośrednich wersji utworzonych}} przez {{PLURAL:$2|jednego użytkownika|$2 użytkowników}})',
+'difference-missing-revision' => '{{PLURAL:$2|Wersja|$2 wersje|$2 wersji}} #$1 strony "{{PAGENAME}}" nie {{PLURAL:$2|została znaleziona|zostały znalezione|zostało znalezionych}}.
+
+Zazwyczaj jest to spowodowane przestarzałym linkiem do usuniętej strony. Powód usunięcia znajduje się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze].',
 
 # Search results
 'searchresults' => 'Wyniki wyszukiwania',
@@ -1539,6 +1552,7 @@ Jeśli zdecydujesz się je podać, zostaną użyte, by udokumentować Twoje auto
 'right-writeapi' => 'Zapis poprzez interfejs API',
 'right-delete' => 'Usuwanie stron',
 'right-bigdelete' => 'Usuwanie stron z długą historią edycji',
+'right-deletelogentry' => 'Usuwanie i przywracanie wpisów rejestru',
 'right-deleterevision' => 'Usuwanie i odtwarzanie określonej wersji strony',
 'right-deletedhistory' => 'Podgląd usuniętych wersji, bez przypisanego im tekstu',
 'right-deletedtext' => 'Podgląd usuniętego tekstu i zmian pomiędzy usuniętymi wersjami',
@@ -1841,7 +1855,7 @@ Jeśli problem będzie się powtarzał, skontaktuj się z [[Special:ListUsers/sy
 'backend-fail-connect' => 'Nie można nawiązać połączenia do wewnętrznych funkcji magazynowania "$1".',
 'backend-fail-internal' => 'Wystąpił nieznany błąd w wewnętrznych funkcjach magazynowania "$1".',
 'backend-fail-contenttype' => 'Nie można określić typ zawartości pliku do przechowywania w "$1".',
-'backend-fail-batchsize' => 'Wewnętrzne funkcje magazynowania otrzymały $1 {{PLURAL:$1|operację|operacje|operacji}} na pliku; limit wynosi $2 {{PLURAL:$2| operacja|operacje|operacji}}.',
+'backend-fail-batchsize' => 'Wewnętrzne funkcje magazynowania otrzymały $1 {{PLURAL:$1|operację|operacje|operacji}} na pliku; limit to $2 {{PLURAL:$2|operacja|operacje|operacji}}.',
 'backend-fail-usable' => 'Nie można zapisać pliku $1 ze względu na niewystarczające uprawnienia lub brak katalogów/kontenerów.',
 
 # File journal errors
@@ -2369,7 +2383,7 @@ Pomoc
 'exblank' => 'Strona była pusta',
 'delete-confirm' => 'Usuwanie „$1”',
 'delete-legend' => 'Usuń',
-'historywarning' => "'''Uwaga!''' Strona, którą chcesz usunąć, ma w przybliżeniu {{PLURAL:$1|starszą wersję|$1 starsze wersje|$1 starszych wersji}}:",
+'historywarning' => "'''Uwaga!''' Strona, którą chcesz usunąć, ma w przybliżeniu {{PLURAL:$1|jedną starszą wersję|$1 starsze wersje|$1 starszych wersji}}:",
 'confirmdeletetext' => 'Zamierzasz usunąć stronę razem z całą dotyczącą jej historią.
 Upewnij się, czy na pewno chcesz to zrobić, że rozumiesz konsekwencje i że robisz to w zgodzie z [[{{MediaWiki:Policy-url}}|zasadami]].',
 'actioncomplete' => 'Operacja wykonana',
@@ -2397,6 +2411,8 @@ Bądź ostrożny, ponieważ usunięcie jej może spowodować zakłócenia w prac
 'rollback' => 'Cofnij edycję',
 'rollback_short' => 'Cofnij',
 'rollbacklink' => 'cofnij',
+'rollbacklinkcount' => 'cofnij $1 {{PLURAL:$1|edycję|edycje|edycji}}',
+'rollbacklinkcount-morethan' => 'cofnij więcej niż $1 {{PLURAL:$1|edycję|edycje|edycji}}',
 'rollbackfailed' => 'Nie udało się cofnąć zmiany',
 'cantrollback' => 'Nie można cofnąć edycji, ponieważ jest tylko jedna wersja tej strony.',
 'alreadyrolled' => 'Nie można dla strony [[:$1|$1]] cofnąć ostatniej zmiany, którą wykonał [[User:$2|$2]] ([[User talk:$2|dyskusja]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]).
@@ -2484,7 +2500,7 @@ Obecne ustawienia dla strony '''$1''' to:",
 'undeletepage' => 'Odtwarzanie usuniętych stron',
 'undeletepagetitle' => "'''Poniżej znajdują się usunięte wersje strony [[:$1]]'''.",
 'viewdeletedpage' => 'Zobacz usunięte wersje',
-'undeletepagetext' => '{{PLURAL:$1|Następująca strona została usunięta, ale jej|Następujące $1 strony zostały usunięte, ale ich}} kopia wciąż znajduje się w archiwum.
+'undeletepagetext' => '{{PLURAL:$1|Następująca strona została usunięta, ale jej kopia wciąż znajduje|Następujące $1 strony zostały usunięte, ale ich kopie wciąż znajdują|Następujące $1 stron zostało usuniętych, ale ich kopie wciąż znajdują}} się w archiwum.
 Archiwum co jakiś czas może być oczyszczane.',
 'undelete-fieldset-title' => 'Odtwarzanie wersji',
 'undeleteextrahelp' => "Jeśli chcesz odtworzyć całą historię edycji strony, pozostaw wszystkie pola niezaznaczone i kliknij '''''{{int:undeletebtn}}'''''.
@@ -3999,7 +4015,7 @@ W przeciwnym wypadku można użyć prostego formularza poniżej. Komentarz zosta
 'api-error-duplicate' => '{{PLURAL:$1|Jest już [$2 inny plik]|Są już [$2 inne pliki]}} o tej samej zawartości',
 'api-error-duplicate-archive' => '{{PLURAL:$1|Był już [$2 inny plik]|Były już [$2 inne pliki]}} o takiej samej zawartości, ale {{PLURAL:$1|został usunięty|zostały usunięte}}.',
 'api-error-duplicate-archive-popup-title' => '{{PLURAL:$1|Zdublowany plik, który został już usunięty|Zdublowane pliki, które zostały już usunięte}}',
-'api-error-duplicate-popup-title' => '{{PLURAL:$1|Zdublowany plik|Zdublowane plik}}',
+'api-error-duplicate-popup-title' => '{{PLURAL:$1|Zdublowany plik|Zdublowane pliki}}',
 'api-error-empty-file' => 'Przesłany przez Ciebie plik jest pusty.',
 'api-error-emptypage' => 'Tworzenie nowych, pustych stron jest niedozwolone.',
 'api-error-fetchfileerror' => 'Błąd wewnętrzny – wystąpił błąd w trakcie pobierania pliku.',
index e163bcf..175c101 100644 (file)
@@ -64,9 +64,9 @@ $messages = array(
 'tog-previewontop' => 'Smon-e la preuva dzora al quàder ëd modìfica dël test e nen sota',
 'tog-previewonfirst' => 'Smon na preuva la prima vira che as fa na modìfica',
 'tog-nocache' => 'Disabilité la memòria local ëd le pàgine dël navigador',
-'tog-enotifwatchlistpages' => "Mand-me un messagi an pòsta eletrònica quand a-i son dle modìfiche a le pàgine ch'im ten-o sot euj",
+'tog-enotifwatchlistpages' => "Mandeme un mëssagi an pòsta eletrònica quand a-i son dle modìfiche a le pàgine ch'im ten-o sot euj",
 'tog-enotifusertalkpages' => 'Mand-me un messagi ëd pòsta eletrònica quand a-i son dle modìfiche a mia pàgina dle ciaciarade',
-'tog-enotifminoredits' => 'Mand-me un messagi an pòsta eletrònica bele che për le modìfiche cite',
+'tog-enotifminoredits' => "Mandeme un mëssagi an pòsta eletrònica bele che për le modìfiche cite dle pàgine o dj'archivi",
 'tog-enotifrevealaddr' => 'Lassa che a së s-ciàira mia adrëssa ëd pòsta eletrònica ant ij messagi ëd notìfica',
 'tog-shownumberswatching' => "Smon ël nùmer d'utent che as ten-o la pàgina sot euj",
 'tog-oldsig' => 'Firma esistenta:',
@@ -306,6 +306,10 @@ $1",
 'youhavenewmessages' => "A l'ha $1 ($2).",
 'newmessageslink' => 'mëssagi neuv',
 'newmessagesdifflink' => "A-i é chèich-còs ëd diferent da 'nt l'ùltima revision",
+'youhavenewmessagesfromusers' => "It l'has $1 da {{PLURAL:$3|n'autr utent|$3 utent}} ($2).",
+'youhavenewmessagesmanyusers' => "It l'has $1 da vaire utent ($2).",
+'newmessageslinkplural' => '{{PLURAL:$1|un mëssagi neuv|$1 mëssagi neuv}}',
+'newmessagesdifflinkplural' => 'ùltim {{PLURAL:$1|cambiament|cambiament}}',
 'youhavenewmessagesmulti' => "A l'ha dij neuv mëssagi an $1",
 'editsection' => 'modìfica',
 'editold' => 'modìfica',
@@ -453,6 +457,7 @@ Che as dësmentia pa ëd cambié ij [[Special:Preferences|"sò gust" an {{SITENA
 'remembermypassword' => "Vis-te mia ciav ansima a st'ordinator-sì (për al pi $1 {{PLURAL:$1|di|di}})",
 'securelogin-stick-https' => "Resté colegà an HTTPS apress d'esse intrà ant ël sistema",
 'yourdomainname' => 'Sò domini',
+'password-change-forbidden' => 'It peule pa cangé le ciav dzora a sta wiki-sì.',
 'externaldberror' => "Ò che a l'é rivaje n'eror d'autenticassion esterna, ò pura a l'é chiel (chila) che a l'é nen autorisà a agiornesse sò cont estern.",
 'login' => 'Rintré ant ël sistema',
 'nav-login-createaccount' => 'rintré ant ël sistema',
@@ -693,6 +698,9 @@ o purament [{{fullurl:{{FULLPAGENAME}}|action=edit}} modìfiché la pàgina ades
 'noarticletext-nopermission' => 'Al moment a-i é pa gnun test an sta pàgina-sì.
 It peule [[Special:Search/{{PAGENAME}}|sërché sto tìtol ëd pàgina-sì]] an d\'àutre pàgine,
 o <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sërché j\'argistrassion colegà]</span>.',
+'missing-revision' => "La revision #\$1 dla pagina ciamà \"{{PAGENAME}}\" a esist pa.
+
+Sòn a l'é normalment causà da l'andé dré a un vej colegament stòrich a na pagina ch'a l'é staita canselà. Ij detaj a peulo esse trovà ant ël [registr ëd le canselassion ëd {{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}}].",
 'userpage-userdoesnotexist' => 'Lë stranòm "<nowiki>$1</nowiki>" a l\'é pa registrà. Për piasì ch\'a varda se da bon a veul creé/modifiché sta pàgina.',
 'userpage-userdoesnotexist-view' => 'Ël cont utent "$1" a l\'é pa registrà.',
 'blocked-notice-logextract' => "S'utent a l'é al moment blocà.
@@ -818,6 +826,7 @@ Costi paràmeter a son stàit lassà fòra.",
 'expansion-depth-exceeded-warning' => "La pàgina a l'ha sorpassà la profondità d'espansion",
 'parser-unstrip-loop-warning' => 'Trovà un sicl nen dësmontàbil',
 'parser-unstrip-recursion-limit' => "Sorpassà ël lìmit d'arcorensa nen dësmontàbil: $1",
+'converter-manual-rule-error' => 'Eror trovà ant la régola ëd conversion manual ëd la lenga',
 
 # "Undo" feature
 'undo-success' => "Sta modìfica-sì as peul scancelesse. Për piasì, ch'a contròla ambelessì sota për esse sigur che a l'é pro lòn che a veul fé, e peuj ch'as salva lòn ch'a l'ha butà chiel/chila për finì dë scancelé la modìfica ch'a-i era.",
@@ -1002,6 +1011,11 @@ Ch'a varda mach che a-i ven-a nen fòra un rabel ant la continuità stòrica.",
 'editundo' => "buta 'me ch'a l'era",
 'diff-multi' => "({{PLURAL:$1|Na revision antërmedia|$1 revision antërmedie}} ëd {{PLURAL:$2|n'utent|$2 utent}} pa mostrà)",
 'diff-multi-manyusers' => "({{PLURAL:$1|Na revision antërmedia|$1 revision antërmedie}} da pi che $2 {{PLURAL:$2|n'utent|utent}} pa mostrà)",
+'difference-missing-revision' => "{{PLURAL:$2|Na revision|$2 revision}} dë sta diferensa ($1) a {{PLURAL:$2|l'é pa stàita|son pa stàite}} trovà.
+
+
+
+Sòn a l'é normalment causà da l'andé dré a un vej colegament stòrich a na pagina ch'a l'é staita canselà. Ij detaj a peulo esse trovà ant ël [registr ëd le canselassion ëd {{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}}].",
 
 # Search results
 'searchresults' => "Arzultà dl'arserca",
@@ -1272,6 +1286,7 @@ Costa anformassion a sarà pùblica.",
 'right-writeapi' => "Dovré l'API dë scritura",
 'right-delete' => 'Scancelé dle pàgine',
 'right-bigdelete' => 'Scancelé dle pàgine con na stòria longa',
+'right-deletelogentry' => 'Scancelé e ripristiné dle vos ëd registr specìfiche',
 'right-deleterevision' => 'Scancelé e disdëscancelé na version ëspessìfica ëd na pàgina',
 'right-deletedhistory' => 'Vardé le revision ëscancelà ëd la stòria, sensa sò test',
 'right-deletedtext' => 'Vëdde ël test ëscancelà e le modìfiche antra le revision ëscancelà',
@@ -2133,6 +2148,8 @@ ch'a daga da ment a lòn ch'a fa.",
 'rollback' => 'Gavé via le modìfiche',
 'rollback_short' => 'Ripristiné',
 'rollbacklink' => "ripristiné j'archivi",
+'rollbacklinkcount' => 'tiré andré $1 {{PLURAL:$1|modìfica|modìfiche}}',
+'rollbacklinkcount-morethan' => 'tiré andré pi che $1 {{PLURAL:$1|modìfica|modìfiche}}',
 'rollbackfailed' => "A l'é pa podusse ripristiné",
 'cantrollback' => "As peul pa tornesse a na version pì veja: l'ùltima modìfica a l'ha fala l'ùnich utent che a l'abia travajà a cost artìcol-sì.",
 'alreadyrolled' => "As peulo pa anulé j'ultime modìfiche ëd [[:$1]] fàite da [[User:$2|$2]] ([[User talk:$2|Talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);
@@ -2686,10 +2703,10 @@ Për piasì, ch'a preuva torna.",
 'tooltip-pt-userpage' => 'Soa pàgina utent',
 'tooltip-pt-anonuserpage' => "La pàgina utent për l'IP con ël qual chiel a contribuiss",
 'tooltip-pt-mytalk' => 'Soa pàgina ëd discussion e ciaciarade',
-'tooltip-pt-anontalk' => 'Pàgina ëd ciaciarade për l',
+'tooltip-pt-anontalk' => 'La pàgina ëd ciaciarade an sle contribussion da costa adrëssa IP',
 'tooltip-pt-preferences' => 'Coma che i veuj mia {{SITENAME}}.',
 'tooltip-pt-watchlist' => 'Lista dle pàgine che chiel as ten sot euj.',
-'tooltip-pt-mycontris' => 'Lista ëd toe contribussion',
+'tooltip-pt-mycontris' => 'Lista ëd soe contribussion',
 'tooltip-pt-login' => "Un a l'é nen obligà a rintré ant al sistema, ma se a lo fa a l",
 'tooltip-pt-anonlogin' => "Un a l'é nen obligà a rintré ant al sistema, ma se a lo fa a l",
 'tooltip-pt-logout' => 'Seurte da',
index afb513d..f4d4539 100644 (file)
@@ -753,6 +753,7 @@ $1',
 'previewnote' => "'''هېر مو نه شي چې دا يواځې يوه مخليدنه ده.'''
 ستاسې لخوا ترسره شوي بدلونونه لا تر اوسه پورې نه دي خوندي شوي!!",
 'editing' => 'د $1 سمونه',
+'creating' => '$1 جوړېدنې کې دی',
 'editingsection' => 'سمونه $1 (برخه)',
 'editingcomment' => 'د $1 سمون (نوې برخه)',
 'editconflict' => 'په سمادولو کې خنډ: $1',
@@ -804,6 +805,9 @@ $1',
 'post-expand-template-inclusion-warning' => "'''ګواښنه:''' دا کينډۍ د خپل ټاکلي بريد نه ډېره لويه ده.
 ځينې کينډۍ به په کې ګډې نه شي.",
 'post-expand-template-inclusion-category' => 'هغه مخونه چې په کې د کارېدلو کينډيو شمېر له ټاکلې کچې ډېر دی',
+'post-expand-template-argument-warning' => "'''ګواښنه:''' دا مخ لږ تر لږه د يوې کينډۍ عاملين لري چې بې حده لوی دی.
+دا عاملين ړنګ شول.",
+'post-expand-template-argument-category' => 'هغه مخونه چې د کينډۍ ړنګ شوي عاملين لري.',
 
 # "Undo" feature
 'undo-norev' => 'دا سمون ناکړ کېدلای نه شي دا ځکه چې دا سمون نشته او يا هم ړنګ شوی.',
@@ -996,7 +1000,7 @@ $1',
 'saveprefs' => 'خوندي کول',
 'resetprefs' => 'بيا سمول',
 'restoreprefs' => 'ټولې تلواليزې امستنې پرځای کول',
-'prefs-editing' => 'د Ø³Ù\85Ù\88Ù\84Ù\88 Ù¾Ù\87 Ø­Ø§Ù\84 Ú©Û\90',
+'prefs-editing' => 'سÙ\85Û\90دÙ\86Û\90 Ú©Û\90 Ø¯Û\8c',
 'prefs-edit-boxsize' => 'د سمون کړکۍ کچه.',
 'rows' => 'ليکې:',
 'columns' => 'ستنې:',
@@ -2006,7 +2010,7 @@ $UNWATCHURL  نه ليدنه وکړۍ
 'tooltip-ca-move' => 'همدا مخ لېږدول',
 'tooltip-ca-watch' => 'دا مخ په خپل کتنلړکې ګډول',
 'tooltip-ca-unwatch' => 'همدا مخ خپل کتنلړ نه لرې کول',
-'tooltip-search' => 'د {{SITENAME}} لټون',
+'tooltip-search' => '{{SITENAME}} پلټل',
 'tooltip-search-go' => 'په دې نوم د کټ مټ ورته مخ شتون په صورت کې، هماغه مخ ته ورځه',
 'tooltip-search-fulltext' => 'په مخونو کې دا متن وپلټه',
 'tooltip-p-logo' => 'لومړی مخ',
@@ -2391,14 +2395,14 @@ $5
 'autosumm-new' => 'د "$1" تورو مخ جوړ شو',
 
 # Live preview
-'livepreview-loading' => 'د Ø¨Ø±Ø³Û\90رÛ\90دÙ\84Ù\88 Ù¾Ù\87 Ø­Ø§Ù\84 Ú©Û\90...',
+'livepreview-loading' => 'برسÛ\90رÛ\90دÙ\86Û\90 Ú©Û\90 Ø¯Û\8c...',
 'livepreview-ready' => 'برسېرېدنه ... چمتو ده!',
 
 # Watchlist editor
 'watchlistedit-noitems' => 'ستاسې کتنلړ کې هېڅ کوم سرليک نشته.',
 'watchlistedit-normal-title' => 'کتنلړ سمول',
 'watchlistedit-normal-legend' => 'د کتنلړ نه سرليکونه لرې کول',
-'watchlistedit-normal-submit' => 'سرليکونه لرکول',
+'watchlistedit-normal-submit' => 'سرليکونه لرې کول',
 'watchlistedit-normal-done' => '{{PLURAL:$1|1 سرليک ستاسې له کتنلړ نه ليري شو|$1 سرليکونه ستاسې له کتنلړ نه ليري شوه}}:',
 'watchlistedit-raw-title' => 'خام کتنلړ سمول',
 'watchlistedit-raw-legend' => 'خام کتنلړ سمول',
@@ -2430,6 +2434,9 @@ $5
 # Signatures
 'signature' => '[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|خبرې اترې]])',
 
+# Core parser functions
+'duplicate-defaultsort' => '\'\'\'ګواښنه:\'\'\'د "$2" تلواليزه اوډون تڼۍ تر دې پخوا ټاکلې تلواليزه اوډون تڼۍ "$1" پر ځای چارنه کېږي.',
+
 # Special:Version
 'version' => 'بڼه',
 'version-extensions' => 'لګېدلي شاتاړي',
index 877c365..85779c5 100644 (file)
@@ -574,6 +574,10 @@ Consulte a página da [[Special:Version|versão do sistema]].',
 'youhavenewmessages' => 'Tem $1 ($2).',
 'newmessageslink' => 'mensagens novas',
 'newmessagesdifflink' => 'comparar com a penúltima revisão',
+'youhavenewmessagesfromusers' => 'Você tem $1 de {{PLURAL:$3|outro utilizador|$3 utilizadores}} ($2).',
+'youhavenewmessagesmanyusers' => 'Você tem $1 de muitos utilizadores ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|uma mensagem nova|mensagens novas}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|última alteração|últimas alterações}}',
 'youhavenewmessagesmulti' => 'Tem mensagens novas em $1',
 'editsection' => 'editar',
 'editold' => 'editar',
@@ -724,6 +728,7 @@ Não se esqueça de personalizar as suas [[Special:Preferences|preferências na
 'remembermypassword' => 'Recordar os meus dados neste computador (no máximo, por $1 {{PLURAL:$1|dia|dias}})',
 'securelogin-stick-https' => 'Manter a ligação HTTPS após a autenticação',
 'yourdomainname' => 'O seu domínio:',
+'password-change-forbidden' => 'Não podes alterar senhas nesta wiki.',
 'externaldberror' => 'Ocorreu um erro externo à base de dados durante a autenticação ou não lhe é permitido actualizar a sua conta externa.',
 'login' => 'Autenticação',
 'nav-login-createaccount' => 'Entrar / criar conta',
@@ -973,6 +978,10 @@ ou [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar esta página]</span>.',
 'noarticletext-nopermission' => 'Ainda não existe texto nesta página.
 Pode [[Special:Search/{{PAGENAME}}|pesquisar o título desta página]] noutras páginas
 ou <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} procurar registos relacionados]</span>.',
+'missing-revision' => 'A revisão #$1 da página denominada "{{PAGENAME}}" não existe.
+
+Isto é geralmente causado por seguir um link de histórico desatualizado para uma página que foi eliminada.
+Os detalhes podem ser encontrados no [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registo de eliminação].',
 'userpage-userdoesnotexist' => 'A conta "<nowiki>$1</nowiki>" não se encontra registada.
 Verifique se deseja realmente criar ou editar esta página, por favor.',
 'userpage-userdoesnotexist-view' => 'A conta de utilizador "$1" não está registada.',
@@ -1105,6 +1114,7 @@ Estes argumentos foram omitidos.',
 'expansion-depth-exceeded-warning' => 'A página excedeu a profundidade de expansão',
 'parser-unstrip-loop-warning' => 'Foi detectado um ciclo infinito unstrip',
 'parser-unstrip-recursion-limit' => 'Limite de recursão do unstrip excedido ($1)',
+'converter-manual-rule-error' => 'Erro detetado na regra de conversão de língua manual',
 
 # "Undo" feature
 'undo-success' => 'É possível desfazer a edição.
@@ -1298,6 +1308,10 @@ Note que, se usar os links de navegação, os botões de opção voltarão aos v
 'editundo' => 'desfazer',
 'diff-multi' => '({{PLURAL:$1|Uma edição intermédia|$1 edições intermédias}} de {{PLURAL:$2|um utilizador|$2 utilizadores}} {{PLURAL:$1|não apresentada|não apresentadas}})',
 'diff-multi-manyusers' => '({{PLURAL:$1|Uma edição intermédia|$1 edições intermédias}} de mais de {{PLURAL:$2|um utilizador|$2 utilizadores}} não {{PLURAL:$1|apresentada|apresentadas}})',
+'difference-missing-revision' => '{{PLURAL:$2|Uma revisão|$2 revisões}} desta diferença ($1) não {{PLURAL:$2|foi encontrada|foram encontradas}}.
+
+Isto é geralmente causado por seguir um link de histórico desatualizado para uma página que foi eliminada.
+Os detalhes podem ser encontrados no [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registo de eliminação].',
 
 # Search results
 'searchresults' => 'Resultados da pesquisa',
@@ -2436,6 +2450,8 @@ prossiga com precaução.',
 'rollback' => 'Reverter edições',
 'rollback_short' => 'Voltar',
 'rollbacklink' => 'voltar',
+'rollbacklinkcount' => 'reverter $1 {{PLURAL:$1|edição|edições}}',
+'rollbacklinkcount-morethan' => 'reverter mais do que $1 {{PLURAL:$1|edição|edições}}',
 'rollbackfailed' => 'A reversão falhou',
 'cantrollback' => 'Não foi possível reverter a edição; o último contribuidor é o único autor desta página',
 'alreadyrolled' => 'Não foi possível reverter as edições de [[:$1]] por [[User:$2|$2]] ([[User talk:$2|discussão]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);
index c98d802..ef1439e 100644 (file)
@@ -94,6 +94,7 @@
  * @author Purodha
  * @author Rancher
  * @author Raymond
+ * @author Reedy
  * @author Robby
  * @author Rotemliss
  * @author Ryan Schmidt
@@ -548,6 +549,18 @@ The format is: "{{int:youhavenewmessages| [[MediaWiki:Newmessageslink/{{SUBPAGEN
 
 {{Identical|New messages}}',
 'newmessagesdifflink' => 'This is the second link displayed in an orange rectangle when a user gets a message on his talk page. Used in message {{msg-mw|youhavenewmessages}} (as parameter $2).',
+'youhavenewmessagesfromusers' => 'New talk indicator message: the message appearing when someone edited your user talk page.
+The message takes three parameters; 
+*$1 {{msg-mw|newmessageslinkplural}}, 
+*$2 {{msg-mw|newmessagesdifflinkplural}}, and 
+*$3 the number of authors who have edited the talk page since the owning user last viewed it.',
+'youhavenewmessagesmanyusers' => 'New talk indicator message: the message appearing when someone edited your user talk page.
+Like {{msg-mw|youhavenewmessages}}, but getting {{msg-mw|newmessageslinkplural}} and {{msg-mw|newmessagesdifflinkplurl}} as parameters $1 and $2, respectively. Used when more than 10 users edited the user talk page since the owning user last viewed it.',
+'newmessageslinkplural' => 'Like {{msg-mw|newmessageslink}} but supporting pluralization. Used in message {{msg-mw|youhavenewmessagesfromusers}} (as parameter $1).
+This message itself takes one parameter, $1, which is 1 if there was one new edit, or 2 if there was more than one new edit
+since the last time the user has seen his or her talk page.',
+'newmessagesdifflinkplural' => 'Like {{msg-mw|newmessagesdifflink}} but supporting pluralization. Used in message {{msg-mw|youhavenewmessagesfromusers}} (as parameter $2).
+This message itself takes one parameter, $1, which is the number of new edits since the last time the user has seen his or her talk page.',
 'youhavenewmessagesmulti' => 'The alternative of {{msg|youhavenewmessages}} as used on wikis with a special setup so they can receive the "new message" notice on other wikis as well. Used on [http://www.wikia.com/ Wikia].
 The format is: "{{int:youhavenewmessagesmulti| [[MediaWiki:Newmessageslink/{{SUBPAGENAME}}|{{int:newmessageslink}}]]}}"',
 'editsection' => 'Display name of link to edit a section on a content page. Example: [{{MediaWiki:Editsection}}].
@@ -1317,10 +1330,12 @@ This message has sometimes a tooltip {{msg-mw|tooltip-undo}}',
 Example: [http://translatewiki.net/w/i.php?title=Project:News&diff=426850&oldid=99999999 Diff with invalid revision#]
 
 * $1 is the list of missing revisions IDs
-* $2 is the number of items in $1',
+* $2 is the number of items in $1 (one or two)',
 
 # Search results
-'searchresults' => '{{Identical|Search results}}',
+'searchresults' => 'This is the title of the page that contains the results of a search.
+
+{{Identical|Search results}}',
 'searchresults-title' => 'Appears as page title in the html header of the search result special page.',
 'notitlematches' => 'Header of results page after a search for a title for which no page exists',
 'textmatches' => 'When displaying search results',
@@ -4807,5 +4822,10 @@ $4 is the gender of the target user.',
 'api-error-verification-error' => 'The word "extension" refers to the part behind the last dot in a file name, that by convention gives a hint about the kind of data format which a files contents are in.',
 
 # Unknown messages
-'api-error-filetype-banned-type' => 'API error message that can be used for client side localisation of API errors.',
+'api-error-filetype-banned-type' => "API error message that can be used for client side localisation of API errors.
+
+* $1 is the extension(s) of the file which cannot be uploaded
+* $2 is the list of file extensions that can be uploaded (Example: ''png, gif, jpg, jpeg, ogg, pdf, svg.'')
+* $3 is the number of allowed file formats (to be used for the PLURAL function)
+* $4 is the number of extensions that could not be uploaded (to be used for the PLURAL function)",
 );
index 13aa830..ff725b6 100644 (file)
  * @author Aryaz
  * @author Dalinanir
  * @author Jose77
+ * @author MoubarikBelkasim
  * @author Urhixidur
  */
 
 $messages = array(
 # Dates
+'sunday' => 'Asamas (Eřḥedd)',
+'monday' => 'Aynas (Řetnayen)',
+'tuesday' => 'Asinas (Ettřata)',
+'wednesday' => 'Akṛas (Řarbeɛ)',
+'thursday' => 'Akwas (Řexmis)',
+'friday' => 'Asimwas (Ejjemɛa)',
+'saturday' => 'Asiḍyas (Esseft)',
 'sun' => 'Asamas',
 'mon' => 'Aynas',
 'tue' => 'Asinas',
@@ -26,15 +34,15 @@ $messages = array(
 'january' => 'Yennayer',
 'february' => 'Yebrayer',
 'march' => 'Mares',
-'april' => 'Ibrir',
+'april' => 'Abril',
 'may_long' => 'May',
 'june' => 'Yunyu',
 'july' => 'Yulyuz',
-'august' => 'Ghuct',
-'september' => 'Cutanbir',
+'august' => 'Ɣuct',
+'september' => 'Cutenbir',
 'october' => 'Ktubar',
 'november' => 'Nuwanbir',
-'december' => 'Dujanbir',
+'december' => 'Dujembir',
 'january-gen' => 'Ynnayr',
 'february-gen' => 'Ybrayr',
 'march-gen' => 'Mars',
@@ -50,15 +58,15 @@ $messages = array(
 'jan' => 'Yennayer',
 'feb' => 'Yebrayer',
 'mar' => 'Mars',
-'apr' => 'Ybrir',
+'apr' => 'Abrir',
 'may' => 'May',
 'jun' => 'Yunyu',
 'jul' => 'Yulyuz',
-'aug' => 'Ghuct',
-'sep' => 'Cutanbir',
+'aug' => 'Ɣuct',
+'sep' => 'Cutembir',
 'oct' => 'Ktubar',
 'nov' => 'Nuwanbir',
-'dec' => 'Dujanbir',
+'dec' => 'Dujenbir',
 
 # Categories related messages
 'category_header' => 'Tasniwin di taggayt "$1"',
@@ -78,6 +86,14 @@ $messages = array(
 'qbedit' => 'Ẓṛeg',
 'qbspecialpages' => 'Tudmawin Special',
 
+# Vector skin
+'vector-action-move' => 'Smuṭṭi',
+'vector-view-create' => 'Seɣnu',
+'vector-view-edit' => 'Ẓṛeg',
+'vector-view-history' => 'Ẓeṛ amezruy',
+'vector-view-view' => 'Ɣeṛ',
+'actions' => 'Timegga',
+
 'errorpagetitle' => 'Anezri',
 'returnto' => 'Dwl ghar $1.',
 'tagline' => 'Zi {{SITENAME}}',
@@ -95,7 +111,7 @@ $messages = array(
 'editthispage' => 'Ẓṛg tasna ya',
 'delete' => 'Kks',
 'protect' => 'Mstn',
-'protect_change' => 'sbadl',
+'protect_change' => 'beddeř',
 'newpage' => 'Tasna d-tamaynut',
 'talkpage' => 'Siwl xf tasna ya',
 'talkpagelinktext' => 'Awal',
@@ -120,11 +136,11 @@ $messages = array(
 'disclaimerpage' => 'Project:Asmigel amatu',
 'edithelp' => 'Tallalt deg uẓareg',
 'edithelppage' => 'Help:Aẓareg',
-'helppage' => 'Help:tallalt',
+'helppage' => 'Help:Tira d yiwlafen',
 'mainpage' => 'Tasna Tamezwarut',
-'mainpage-description' => 'Tasna Tamzwarut',
+'mainpage-description' => 'Tasna Tamezwarut',
 'portal' => 'Tawwart n timetti',
-'portal-url' => 'Project:tawwart n timetti',
+'portal-url' => 'Project:tawwart n yiwdan',
 'privacy' => 'Tasertit n tusligi',
 'privacypage' => 'Project:Tasertit n tusligi',
 
@@ -134,6 +150,7 @@ $messages = array(
 'newmessagesdifflink' => 'Taẓṛigt tanggarut',
 'editsection' => 'Ẓṛeg',
 'editold' => 'ẓṛeg',
+'viewsourceold' => 'ẓeṛ aɣbalu',
 'editlink' => 'ẓṛg',
 'viewsourcelink' => 'ẓṛ aghbalu',
 'editsectionhint' => 'Ẓṛeg tigezmi: $1',
@@ -141,23 +158,23 @@ $messages = array(
 'showtoc' => 'sskn-d',
 'hidetoc' => 'snuffar',
 'site-rss-feed' => 'Tilgha n RSS n $1',
-'site-atom-feed' => 'Talghut n Atom n $1',
+'site-atom-feed' => 'Talɣut n Atom n $1',
 'page-rss-feed' => 'Asudem n RSS n "$1"',
-'red-link-title' => '$1 (tasna ur telli)',
+'red-link-title' => '$1 (tasna wer telli)',
 
 # Short words for each namespace, by default used in the namespace tab in monobook
 'nstab-main' => 'Tasna',
 'nstab-user' => 'Tasna n User',
 'nstab-project' => 'Tasna usenfar',
 'nstab-image' => 'Asatul',
-'nstab-template' => 'Tamudmt',
+'nstab-template' => 'Tamudemt',
 'nstab-category' => 'Taggayt(category)',
 
 # General errors
 'badtitle' => 'isem war icni ca',
 'badtitletext' => 'Isem n Tasna itexised war icni ca, ixwa, niɣ  isem n ajar-tutlayt niɣ ajar-wiki war icni ca.
 teqqad ad yilli days ca n usekkil war itwagg deg isem .',
-'viewsource' => 'Ẓṛ aghbalu',
+'viewsource' => 'Ẓeṛ aɣbalu',
 'viewsourcetext' => 'Tzemred a tẓerd u atsneɣled aɣbal n Tasna ya :',
 
 # Login and logout pages
@@ -299,10 +316,10 @@ Tasarut: (cur) = imṣebḍiyen ag tunɣilt n ruxa,
 'rev-delundel' => 'sken/ffer',
 
 # Diffs
-'history-title' => 'Amezruy n ufegged n "$1"',
+'history-title' => 'Amezruy n teẓṛigt n "$1"',
 'lineno' => 'Tabrit $1:',
 'compareselectedversions' => 'Smequdda tunɣilin a',
-'editundo' => 'kkes min ggigh',
+'editundo' => 'kkes min ggiɣ',
 'diff-multi' => '({{PLURAL:$1|ijj n ufegged|$1 ifeggiden}} war ad twamlen ca.)',
 
 # Search results
@@ -312,7 +329,11 @@ Tasarut: (cur) = imṣebḍiyen ag tunɣilt n ruxa,
 'prevn' => 'Amzray {{PLURAL:$1|$1}}',
 'nextn' => 'wn d-itasn {{PLURAL:$1|$1}}',
 'viewprevnext' => 'Ẓeṛ ($1 {{int:pipe-separator}} $2) ($3)',
+'searchprofile-everything' => 'Marra',
+'searchprofile-articles-tooltip' => 'Rzu di $1',
+'searchprofile-project-tooltip' => 'Rzu di $1',
 'search-result-size' => '$1 ({{PLURAL:$2|1 tawalt|$2 tiwalin}})',
+'search-redirect' => '(awi $1)',
 'search-suggest' => 'Ttugha txsd a tinid: $1',
 'search-interwiki-caption' => 'Awmatn n usnfar',
 'search-interwiki-more' => '(ujar)',
@@ -341,7 +362,7 @@ mala tucit-id, ataf Lxdant inec a tetwassan ila inec.',
 
 # Recent changes
 'nchanges' => '$1 {{PLURAL:$1|tiẓṛegt|tiẓṛigin}}',
-'recentchanges' => 'Tiẓṛigin tinggura',
+'recentchanges' => 'Tiẓṛigin tineggura',
 'recentchanges-feed-description' => 'Bbar tiẓṛigin timayutin n wiki deg usudem(feed) a .',
 'rcnote' => "Awadday {{PLURAL:$1|d '''1''' taẓṛigt|d '''$1''' tiẓṛigin tinggura}} deg {{PLURAL:$2|ass anggaru |'''$2''' ussan inggura}}, am di $5, $4.",
 'rcnotefrom' => "ɣar wadday d tiẓṛigin zi '''$2''' (ar '''$1''' ).",
@@ -356,7 +377,7 @@ mala tucit-id, ataf Lxdant inec a tetwassan ila inec.',
 'diff' => 'imṣebḍan',
 'hist' => 'Amezruy',
 'hide' => 'Snuffar',
-'show' => 'smmel-ad',
+'show' => 'semmel-d',
 'minoreditletter' => 'm',
 'newpageletter' => 'N',
 'boteditletter' => 'b',
@@ -394,7 +415,7 @@ Tasniwin di [[Special:Watchlist|your watchlist]] d '''tizurarin'''.",
 'filehist-dimensions' => 'Tisektiwin',
 'filehist-filesize' => 'Tiddi n ufaylu',
 'filehist-comment' => 'Tinit',
-'imagelinks' => 'Iwurn n usdaw',
+'imagelinks' => 'Aseqdec usatul',
 'linkstoimage' => '{{PLURAL:$1|Tasna ya teqn-ad|$1 Tasniwin a qnent-id}} ɣa ufaylu ya :',
 'nolinkstoimage' => 'war telli ca n Tasna teqqen-d ɣa ufaylu ya.',
 'sharedupload' => 'Wa d ijj ufaylu itwacrec jar aṭṭas n isenfaren(projects).',
@@ -413,7 +434,7 @@ Tasniwin di [[Special:Watchlist|your watchlist]] d '''tizurarin'''.",
 'unusedtemplates' => 'Timudmiwin war twasexedment',
 
 # Random page
-'randompage' => 'Tasna zi ṭṭarf',
+'randompage' => 'Tasna mamec ma tella',
 
 # Random redirect
 'randomredirect' => '(redirect) zi ṭṭarf',
@@ -559,6 +580,7 @@ Tezemred ad tbedeld aswir n uḥeṭṭu n Tasna ya, maca war itegg walu i uḥe
 
 # Undelete
 'undeletebtn' => 'Ar-ad',
+'undeleteviewlink' => 'ẓeṛ',
 'undelete-search-submit' => 'Tarzzut',
 
 # Namespace form on various pages
@@ -590,7 +612,7 @@ Tezemred ad tbedeld aswir n uḥeṭṭu n Tasna ya, maca war itegg walu i uḥe
 'nolinkshere' => "war tlli ca n Tasna tqqen-d da '''[[:$1]]'''.",
 'isredirect' => 'Tasna n (redirect)',
 'istemplate' => 'Asidef',
-'isimage' => 'amaqqan n tugna',
+'isimage' => 'amaqqan usatul',
 'whatlinkshere-prev' => '{{PLURAL:$1|deffar|deffar $1}}',
 'whatlinkshere-next' => '{{PLURAL:$1|zzat|zzat $1}}',
 'whatlinkshere-links' => '← tizdayin',
@@ -657,11 +679,11 @@ snaqlitent s ufus.'''",
 # Tooltip help for the actions
 'tooltip-pt-userpage' => 'Tasna inu',
 'tooltip-pt-mytalk' => 'Tasna usiwl inu',
-'tooltip-pt-preferences' => 'Isemyifiyen inu',
+'tooltip-pt-preferences' => 'Min d-ac itteɛjiben',
 'tooltip-pt-watchlist' => 'Tabdart n Tasniwin umi txmamd bac ad-ten teẓṛegd',
 'tooltip-pt-mycontris' => 'Umuɣ n tiwuriwin inu',
 'tooltip-pt-login' => 'Neqqar ac adef s umiḍan nnek; maca malla texsed waha',
-'tooltip-pt-logout' => 'Ufugh',
+'tooltip-pt-logout' => 'Ufuɣ',
 'tooltip-ca-talk' => 'Amsawal xef tasna n ukettur',
 'tooltip-ca-edit' => 'Tzemmared a tẓeṛged tasna ya.
 Bbeẓ x ufeskar n uzar-timeẓṛi zzat i gha txemmled min turid',
@@ -675,11 +697,11 @@ Bbeẓ x ufeskar n uzar-timeẓṛi zzat i gha txemmled min turid',
 'tooltip-ca-unwatch' => 'Kkes Tasna ya zi Tabdart uḥṭṭu inec',
 'tooltip-search' => 'Rzu {{SITENAME}}',
 'tooltip-search-go' => 'Uyur ghar tasna s yizwel a s imant nnes malla tella',
-'tooltip-search-fulltext' => 'Rzu di tasniwin x waḍṛis a',
+'tooltip-search-fulltext' => 'Rzu di tasniwin xef waḍṛis a',
 'tooltip-p-logo' => 'Tasbtirt Tamzwarut',
-'tooltip-n-mainpage' => 'Adef ghar tasna tamezwarut',
+'tooltip-n-mainpage' => 'Adef ɣar tasna tamezwarut',
 'tooltip-n-mainpage-description' => 'Adef ghar tasna tamezwarut',
-'tooltip-n-portal' => 'Xf usenfar, mayn tzemmared a tegged, mani gha tafed tighawsiwin',
+'tooltip-n-portal' => 'Xef usenfar, mayen tzemmared ad tegged, mani ɣa tafed tiɣawsiwin',
 'tooltip-n-currentevents' => 'Af tilgha n ugilal xf tmsarin titurawin',
 'tooltip-n-recentchanges' => 'Tabdart n isenfilen imaynuten di Wiki.',
 'tooltip-n-randompage' => 'Zdem ict tasna d tagacurant',
@@ -720,11 +742,11 @@ Bbeẓ x ufeskar n uzar-timeẓṛi zzat i gha txemmled min turid',
 'ilsubmit' => 'Tarzzut',
 
 # Bad image list
-'bad_image_list' => 'Tameslayt amya :
+'bad_image_list' => 'Talɣa tella ammu :
 
-imagraden n tebdart (ɣarsent * deg umzwaru) ig iteten waha.
-Tazdayt tamzwarut di tebrit ixessa atzdi ɣa afaylu war icni  .
-kur tazdayt tdeffad a tmekkas .',
+Imagraden n tebdart (ɣar-sent * deg umezwaru) waha iy yellan nican, inneḍni uhu.
+Amaqqan amezwarutdi tebridt ixessa ad tili teqqen ɣer ijen usatul aɛeffan.
+Marra imaqqanen nneḍni xef ijen uceṛṛid simant nnes ad ilin d tuksawin, amecnaw tasniwin mani izemmer usatul ad d-yeffeɣ deg uceṛṛiḍ.',
 
 # Metadata
 'metadata' => 'Timuca Meta',
index 3158eed..74a379a 100644 (file)
@@ -566,6 +566,10 @@ $1',
 'youhavenewmessages' => 'Aveți $1 ($2).',
 'newmessageslink' => 'mesaje noi',
 'newmessagesdifflink' => 'comparație cu versiunea precedentă',
+'youhavenewmessagesfromusers' => 'Aveți $1 de la {{PLURAL:$3|un alt utilizator|$3 utilizatori}} ($2).',
+'youhavenewmessagesmanyusers' => 'Aveți $1 de la mulți utilizatori ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|un mesaj nou|mesaje noi}}',
+'newmessagesdifflinkplural' => 'ultima/(ele) {{PLURAL:$1|schimbare|schimbări}}',
 'youhavenewmessagesmulti' => 'Aveți mesaje noi la $1',
 'editsection' => 'modificare',
 'editold' => 'modificare',
@@ -1092,6 +1096,11 @@ Acsete argumente au fost omise.',
 'language-converter-depth-warning' => 'Limita adâncimii convertorului de limbă a fost depășită ($1)',
 'node-count-exceeded-category' => 'Pagini unde numărul de noduri este depășit',
 'node-count-exceeded-warning' => 'Pagina a depășit numărul de noduri',
+'expansion-depth-exceeded-category' => 'Pagini unde profunzimea de expansiune este depășită',
+'expansion-depth-exceeded-warning' => 'Pagina depășește profunzimea de expansiune',
+'parser-unstrip-loop-warning' => 'Buclă nedetașabilă detectată',
+'parser-unstrip-recursion-limit' => 'Limita de recursivitate nedetașabilă depășită ($1)',
+'converter-manual-rule-error' => 'Eroare detectată în regula manuală de conversie a limbii',
 
 # "Undo" feature
 'undo-success' => 'Modificarea poate fi anulată. Verificați diferența de dedesupt și apoi salvați pentru a termina anularea modificării.',
@@ -1831,9 +1840,18 @@ Dacă problema persistă, contactați un [[Special:ListUsers/sysop|administrator
 'backend-fail-read' => 'Imposibil de citit fișierul $1.',
 'backend-fail-create' => 'Imposibil de scris fișierul $1.',
 'backend-fail-maxsize' => 'Nu s-a putut scrie fișierul $1 pentru că acesta este mai mare de {{PLURAL:$2|un octet|$2 octeți|$2 de octeți}}.',
+'backend-fail-readonly' => "Suportul de stocare „$1” este în prezent doar în citire. Motivul dat este: „''$2''”",
+'backend-fail-synced' => 'Fișierul „$1” este într-o stare de inconsistență în suporturile de stocare internă',
+'backend-fail-connect' => 'Imposibil de conectat la suportul de stocare „$1”.',
+'backend-fail-internal' => 'O eroare necunoscută s-a produs în suportul de stocare „$1”.',
 'backend-fail-contenttype' => 'Nu s-a putut determina tipul de conținut al fișierului de stocat la „$1”.',
+'backend-fail-batchsize' => 'Suportul de stocare a furnizat un lot de $1 {{PLURAL:$1|operațiune|operațiuni|de operațiuni}} de fișier; limita este $2 {{PLURAL:$2|operațiune|operațiuni|de operațiuni}}.',
 'backend-fail-usable' => 'Imposibil de scris fișierul $1 din cauza permisiunilor insuficiente sau din cauza directoarelor/containerelor lipsă.',
 
+# File journal errors
+'filejournal-fail-dbconnect' => 'Imposibil de conectat la baza de date a jurnalului pentru terminatul de stocare „$1”.',
+'filejournal-fail-dbquery' => 'Imposibil de actualizat baza de date a jurnalului pentru terminalul de stocare „$1”.',
+
 # Lock manager
 'lockmanager-notlocked' => 'Imposibil de deblocat „$1”; nu este blocată.',
 'lockmanager-fail-closelock' => 'Imposibil de închis fișierul de blocare pentru „$1”.',
@@ -1843,6 +1861,7 @@ Dacă problema persistă, contactați un [[Special:ListUsers/sysop|administrator
 'lockmanager-fail-releaselock' => 'Imposibil de eliberat blocarea pentru „$1”.',
 'lockmanager-fail-db-bucket' => 'Imposibil de contactat suficient baza de date cu blocări în găleata $1.',
 'lockmanager-fail-db-release' => 'Imposibil de eliberat blocările din baza de date $1.',
+'lockmanager-fail-svr-acquire' => 'Imposibil de obținut blocări pe serverul $1.',
 'lockmanager-fail-svr-release' => 'Imposibil de eliberat blocările de pe serverul $1.',
 
 # ZipDirectoryReader
@@ -1927,7 +1946,7 @@ Când acestei pagini îi este aplicat filtrul de utilizator, sunt afișate doar
 'filehist' => 'Istoricul fișierului',
 'filehist-help' => "Apăsați pe '''Data și ora''' pentru a vedea versiunea fișierului trimisă la momentul respectiv.",
 'filehist-deleteall' => 'șterge tot',
-'filehist-deleteone' => 'șterge',
+'filehist-deleteone' => 'ștergere',
 'filehist-revert' => 'revenire',
 'filehist-current' => 'actuală',
 'filehist-datetime' => 'Data și ora',
@@ -2069,7 +2088,7 @@ Intrările <del>tăiate</del> au fost rezolvate.',
 'brokenredirects' => 'Redirecționări greșite',
 'brokenredirectstext' => 'Următoarele redirecționări conduc spre articole inexistente:',
 'brokenredirects-edit' => 'modificare',
-'brokenredirects-delete' => 'șterge',
+'brokenredirects-delete' => 'ștergere',
 
 'withoutinterwiki' => 'Pagini fără legături interwiki',
 'withoutinterwiki-summary' => 'Următoarele pagini nu se leagă la versiuni ale lor în alte limbi:',
index 7befd5f..ba7438b 100644 (file)
@@ -296,6 +296,10 @@ Vide [[Special:Version|Versione d'a pàgene]].",
 'youhavenewmessages' => 'Tu tine $1 ($2).',
 'newmessageslink' => 'messàgge nuève',
 'newmessagesdifflink' => 'urteme cangiaminde',
+'youhavenewmessagesfromusers' => "Tu è $1 da {{PLURAL:$3|'n'otre utende|$3 utinde}} ($2).",
+'youhavenewmessagesmanyusers' => 'Tu è $1 da assaije utinde ($2).',
+'newmessageslinkplural' => "{{PLURAL:$1|'nu messàgge nuève|messàgge nuève}}",
+'newmessagesdifflinkplural' => 'urteme {{PLURAL:$1|cangiamende|cangiaminde}}',
 'youhavenewmessagesmulti' => "T'onne arrevete mèssagge nueve sus 'a $1",
 'editsection' => 'cange',
 'editsection-brackets' => '[$1]',
@@ -423,6 +427,10 @@ $2',
 'ns-specialprotected' => 'Le pàgene speciale no ponne essere cangete.',
 'titleprotected' => "Stu titele ha state prutette da 'a ccreazione da [[User:$1|$1]].
 'U mutive jè ''$2''.",
+'invalidtitle-knownnamespace' => 'Titole invalide cu \'u namespace "$2" e teste "$3"',
+'invalidtitle-unknownnamespace' => 'Titele invalide cu numere de namespace scanusciute $1 e teste "$2"',
+'exception-nologin' => 'Non ge sì collegate',
+'exception-nologin-text' => "Sta pàgene o azione richiede ca a trasè jndr'à sta uicchi.",
 
 # Virus scanner
 'virus-badscanner' => "Configurazione ca fece schife: Virus scanner scanusciute: ''$1''",
@@ -443,6 +451,7 @@ No te sce scurdanne de cangià le [[Special:Preferences|{{SITENAME}} preferenze
 'remembermypassword' => "Arrencuerdete 'u nome mije sus a stu combiuter (pe 'nu massime de $1 {{PLURAL:$1|sciurne|sciurne}})",
 'securelogin-stick-https' => "Statte collegate ô HTTPS apprisse 'a trasute",
 'yourdomainname' => "'U nome d'u dominie tue:",
+'password-change-forbidden' => 'Non ge puè cangià le passuord sus a sta uicchi.',
 'externaldberror' => "Vide bbuene, o stè 'n'errore de autendicazione a 'u database oppure tu non ge puè aggiorna 'u cunde tue esterne.",
 'login' => 'Tràse',
 'nav-login-createaccount' => 'Tràse / Reggistrete',
@@ -830,6 +839,9 @@ Ste argumende onne state luete.",
 'node-count-exceeded-warning' => "Pagene ha sbunnate 'u condegge de le node",
 'expansion-depth-exceeded-category' => "Pàggene addò 'a profonnetà de l'espanzione jè supranate",
 'expansion-depth-exceeded-warning' => "Pàggene ca sbonnane 'a profonnetà de espanzione",
+'parser-unstrip-loop-warning' => 'Cicle infinite acchiate',
+'parser-unstrip-recursion-limit' => 'Limite de ricorsione infinite sbunnate ($1)',
+'converter-manual-rule-error' => "Errore assute jndr'à le regole de conversione d'a lènghe manuale",
 
 # "Undo" feature
 'undo-success' => "'U cangiamende pò essere annullate.
@@ -1292,6 +1304,7 @@ Ce tu 'u mitte, a fatje ca è fatte t'avène ricanusciute.",
 'right-writeapi' => 'Ause de le API scritte',
 'right-delete' => 'Scangille le pàggene',
 'right-bigdelete' => "Scangille le pàggene cu 'na storia longa longa",
+'right-deletelogentry' => "Scangille e ripristine vôsce specifiche de l'archivije",
 'right-deleterevision' => 'Scangille o repristine le revisiune specifiche de le pàggene',
 'right-deletedhistory' => "Vide le versiune, d'u cunde, scangellate, senza 'u teste lore associate",
 'right-deletedtext' => "Vide 'u teste scangellate e le cangiaminde 'mbrà le versiune scangellate",
@@ -1597,6 +1610,11 @@ Ce 'u probbleme angore jè presende, condatte 'n'[[Special:ListUsers/sysop|ammin
 'backend-fail-connect' => 'Non ge pozze connettere \'a memorie de rrete "$1".',
 'backend-fail-internal' => "'N'errore scanusciute s'à verificate jndr'à l'archivije de rrete \"\$1\".",
 'backend-fail-contenttype' => 'Non ge pozze capìe \'u tipe de condenute d\'u file da reggistrà sus a "$1".',
+'backend-fail-usable' => 'Non ge pozze scrivere \'u file "$1" purcé mangane le permesse sufficiende o non g\'esiste \'a cartelle/contenitore.',
+
+# File journal errors
+'filejournal-fail-dbconnect' => 'Non ge pozze collegà a l\'archivije d\'u database pe memorizzà \'u rrete "$1".',
+'filejournal-fail-dbquery' => 'Non ge pozze aggiornà l\'archivije d\'u database pe memorizzà \'u rrete "$1".',
 
 # Lock manager
 'lockmanager-notlocked' => 'Non ge pozze sbloccà "$1"; jidde non g\'è bloccate.',
@@ -1607,6 +1625,7 @@ Ce 'u probbleme angore jè presende, condatte 'n'[[Special:ListUsers/sysop|ammin
 'lockmanager-fail-releaselock' => 'Non ge pozze relassà blocche pe "$1".',
 'lockmanager-fail-db-bucket' => "Non ge pozze condattà 'u database purcé stonne troppe blocche jndr'à $1.",
 'lockmanager-fail-db-release' => "Non ge pozze relassà le blocche sus a 'u database $1.",
+'lockmanager-fail-svr-acquire' => "Non ge pozze pigghià le blocche sus a 'u server $1.",
 'lockmanager-fail-svr-release' => "Non ge pozze relassà le blocche sus a 'u server $1.",
 
 # ZipDirectoryReader
@@ -1913,6 +1932,7 @@ Pe piacere note ca otre site de Indernette ponne appondà a 'nu file cu 'na URL
 Tu puè restringere 'a viste selezionanne 'u tipe de archivije, 'u nome utende (senzibbile a le maiuscole), o le pàggene coinvolte (pure chiste senzibbile a le maiuscole).",
 'logempty' => "Non ge stè 'n'anema de priatorie jndr'à l'archivije.",
 'log-title-wildcard' => 'Cirche le titele ca accumenzene cu stu teste',
+'showhideselectedlogentries' => "Fà vedè/scunne le righe scacchiate de l'archivije",
 
 # Special:AllPages
 'allpages' => 'Tutte le pàggene',
@@ -2154,6 +2174,8 @@ và cunge cunge!",
 'rollback' => 'Annulle le cangiaminde',
 'rollback_short' => 'Annulle',
 'rollbacklink' => "annulle 'u cangiaminde",
+'rollbacklinkcount' => 'annulle $1 {{PLURAL:$1|cangiamende|cangiaminde}}',
+'rollbacklinkcount-morethan' => 'annulle cchiù de $1 {{PLURAL:$1|cangiamende|cangiaminde}}',
 'rollbackfailed' => 'Annullamende fallite',
 'cantrollback' => "Non ge se pò annullà stu cangiamende;
 l'urteme condrebbutore jè sulamende l'autore de sta pàgene.",
@@ -2847,6 +2869,7 @@ Stu fatte ha state causate da 'nu collegamende a 'nu site esterne ca appartene a
 'spambot_username' => "Sdevacatore d'u spam de MediaUicchi",
 'spam_reverting' => "Turnanne a l'urtema revisione no ge condiene collegaminde a $1",
 'spam_blanking' => 'Tutte le revisiune condènene collegaminde a $1, vacande',
+'spam_deleting' => 'Tutte le revisiune condènene collegaminde a $1, stoche a scangelle',
 
 # Info page
 'pageinfo-title' => '\'Mbormaziune pe "$1"',
@@ -3903,6 +3926,8 @@ Ce nò, tu puè ausà 'u module facile aqquà sotte. 'U commende tune avène agg
 'api-error-empty-file' => "'U file ca tu è confermate ere vacande.",
 'api-error-emptypage' => 'Quanne se ne ccreje une, le pàggene vacande non ge sò permesse.',
 'api-error-fetchfileerror' => "Errore inderne: Quacchecose ha sciute stuèrte quanne ste analizzave 'u file.",
+'api-error-fileexists-forbidden' => '\'Nu file cu \'u nome "$1" esiste, e non ge pò essere sovrascritte.',
+'api-error-fileexists-shared-forbidden' => "'Nu file cu 'u nome \"\$1\" esiste jndr'à l'archivije de le file comune, e non ge pò essere sovrascritte.",
 'api-error-file-too-large' => "'U file ca tu è confermate jè troppe granne.",
 'api-error-filename-tooshort' => "'U nome d'u file jè troppe curte.",
 'api-error-filetype-banned' => 'Stu tipe de file jè vietate.',
index b585304..c3870fc 100644 (file)
@@ -51,6 +51,7 @@
  * @author KorneySan
  * @author Kv75
  * @author Lockal
+ * @author MaxBioHazard
  * @author MaxSem
  * @author Ola
  * @author Ole Yves
@@ -747,8 +748,8 @@ $1',
 'unexpected' => 'Неподходящее значение: «$1»=«$2».',
 'formerror' => 'Ошибка: невозможно передать данные формы',
 'badarticleerror' => 'Это действие не может быть выполнено на данной странице.',
-'cannotdelete' => 'Невозможно удалить страницу или файл «$1».
\92озможно, Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83же Ð¾ÐºÐ°Ð·Ð°Ð»Ð°Ñ\81Ñ\8c Ñ\83далена.',
+'cannotdelete' => 'Невозможно удалить или переименовать страницу или файл «$1».
\92озможно, Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83же Ð±Ñ\8bла Ñ\83далена Ð¸Ð»Ð¸ Ð¿ÐµÑ\80еименована.',
 'cannotdelete-title' => 'Нельзя удалить страницу «$1»',
 'delete-hook-aborted' => 'Правка отменена процедурой-перехватчиком.
 Дополнительных пояснений не приведено.',
index 49ea186..baf47cc 100644 (file)
@@ -55,11 +55,11 @@ $messages = array(
 'tog-numberheadings' => 'Автоматічно чісловати надписы',
 'tog-showtoolbar' => 'Вказати панел інштрументів (потрібный JavaScript)',
 'tog-editondblclick' => 'Едітовати двоїтым кликом (JavaScript)',
-'tog-editsection' => 'Ð\9fоволити едітованя секції сторінкы через одказ [едіт.]',
-'tog-editsectiononrightclick' => 'Ð\9fоволити едітованя секції сторінкы через кликаня правов клапков мышкы на надписы сторінок  (JavaScript)',
+'tog-editsection' => 'Ð\94озволити едітованя секції сторінкы через одказ [едіт.]',
+'tog-editsectiononrightclick' => 'Ð\94озволити едітованя секції сторінкы через кликаня правов клапков мышкы на надписы сторінок  (JavaScript)',
 'tog-showtoc' => 'Вказовати обсяг (на сторінках з веце як трёма надписами)',
 'tog-rememberpassword' => 'Запамятати моє приголошіня на тім переглядачу (максімално $1 {{PLURAL:$1|день|днів}})',
-'tog-watchcreations' => 'Ð\9fÑ\80идаваÑ\82и Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ñ\81Ñ\82воÑ\80енÑ\8b Ð¼Ð½Ð¾Ð² Ñ\82ай Ñ\84айлÑ\8b Ð¼Ð½Ð¾Ð² Ð½Ð°Ð³Ñ\80аваны до мого списку слїдованых',
+'tog-watchcreations' => 'Ð\9fÑ\80идаваÑ\82и Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ñ\81Ñ\82воÑ\80енÑ\8b Ð¼Ð½Ð¾Ð² Ñ\82ай Ñ\84айлÑ\8b Ð¼Ð½Ð¾Ð² Ð·Ð°Ð»Ð°Ð´Ð¾ваны до мого списку слїдованых',
 'tog-watchdefault' => 'Придавати мнов едітованы сторінкы і файлы до списку слїдованых',
 'tog-watchmoves' => 'Придавати переменованы сторінкы і файлы до мого списку слїдованых',
 'tog-watchdeletion' => 'Придавати сторінкы і файлы, котры змажу, міджі слїдованы',
@@ -179,7 +179,7 @@ $messages = array(
 'moredotdotdot' => 'Детайлнїше…',
 'mypage' => 'Моя сторінка',
 'mytalk' => 'Моя діскузія',
-'anontalk' => 'Діскузія ку тотїй IP-адресі',
+'anontalk' => 'Діскузія к тїй IP-адресї',
 'navigation' => 'Навіґація',
 'and' => '&#32;і',
 
@@ -198,15 +198,15 @@ $messages = array(
 'vector-action-addsection' => 'Придати тему',
 'vector-action-delete' => 'Вымазати',
 'vector-action-move' => 'Переменовати',
-'vector-action-protect' => 'Ð¥Ñ\80анити',
+'vector-action-protect' => 'Ð\92Ñ\81окоÑ\82ити',
 'vector-action-undelete' => 'Обновити',
 'vector-action-unprotect' => 'Змінити замок',
 'vector-simplesearch-preference' => 'Поволити росшырены пропозіції гляданя (лем взгляд Vector )',
 'vector-view-create' => 'Створити',
 'vector-view-edit' => 'Едітовати',
-'vector-view-history' => 'Відїти історію',
+'vector-view-history' => 'Видїти історію',
 'vector-view-view' => 'Чітати',
-'vector-view-viewsource' => 'Відїти код',
+'vector-view-viewsource' => 'Видїти код',
 'actions' => 'Дїї',
 'namespaces' => 'Просторы назв',
 'variants' => 'Варіанты',
@@ -222,7 +222,7 @@ $messages = array(
 'history' => 'Історія сторінкы',
 'history_short' => 'Історія',
 'updatedmarker' => 'змінено од послїдный навщівы',
-'printableversion' => 'Ð\92еÑ\80зÑ\96Ñ\8f Ð¿Ñ\80о Ð´Ñ\80Ñ\83к',
+'printableversion' => 'Ð\92еÑ\80зÑ\96Ñ\8f Ð´Ð¾ Ð´Ñ\80Ñ\83кÑ\83',
 'permalink' => 'Перманентный одказ',
 'print' => 'Друк',
 'view' => 'Видїти',
@@ -234,9 +234,9 @@ $messages = array(
 'deletethispage' => 'Змазати тоту сторінку',
 'undelete_short' => 'Обновити $1 {{PLURAL:$1|верзію|верзії|верзії}}',
 'viewdeleted_short' => 'Видїти {{PLURAL:$1|змазанов едітаціёв|$1 змазаны едітації|$1 змазаных едітацій}}',
-'protect' => 'Ð¥Ñ\80анити',
+'protect' => 'Ð\92Ñ\81окоÑ\82ити',
 'protect_change' => 'змінити',
-'protectthispage' => 'Ð¥Ñ\80анити тоту сторінку',
+'protectthispage' => 'СокоÑ\82ити тоту сторінку',
 'unprotect' => 'Змінити замок',
 'unprotectthispage' => 'Змінити замок той сторінкы',
 'newpage' => 'Нова сторінка',
@@ -245,18 +245,18 @@ $messages = array(
 'specialpage' => 'Шпеціална сторінка',
 'personaltools' => 'Особны інштрументы',
 'postcomment' => 'Нова секція',
-'articlepage' => 'Ð\9fеÑ\80еглÑ\8fднÑ\83ти сторінку',
+'articlepage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити сторінку',
 'talk' => 'Діскузія',
 'views' => 'Перегляды',
 'toolbox' => 'Інштрументы',
-'userpage' => 'Ð\92идÑ\97ти сторінку хоснователя',
-'projectpage' => 'Ð\92идÑ\97ти сторінку проєкту',
-'imagepage' => 'Ð\92идÑ\97ти сторінку файлу',
-'mediawikipage' => 'Ð\92идÑ\97ти сторінку повідомлїнь',
-'templatepage' => 'Ð\92идÑ\97ти шаблону',
-'viewhelppage' => 'Ð\92идÑ\97ти сторінку помочі',
-'categorypage' => 'Ð\92идÑ\97ти сторінку катеґорії',
-'viewtalkpage' => 'Ð\92идÑ\97ти діскузію',
+'userpage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити сторінку хоснователя',
+'projectpage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити сторінку проєкту',
+'imagepage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити сторінку файлу',
+'mediawikipage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити сторінку повідомлїнь',
+'templatepage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити шаблону',
+'viewhelppage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити сторінку помочі',
+'categorypage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити сторінку катеґорії',
+'viewtalkpage' => 'Ð\9fоÑ\81моÑ\82Ñ\80ити діскузію',
 'otherlanguages' => 'Іншыма языками',
 'redirectedfrom' => '(Напрямленый з $1)',
 'redirectpagesub' => 'Сторінка-напрямлїня',
@@ -276,15 +276,15 @@ $1',
 'pool-errorunknown' => 'Незнама хыба',
 
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
-'aboutsite' => 'O  {{grammar:accusative|{{SITENAME}}}}',
-'aboutpage' => 'Project:Описаня',
+'aboutsite' => 'О&nbsp;{{grammar:6sg|{{SITENAME}}}}',
+'aboutpage' => 'Project:О проєктї',
 'copyright' => 'Обсяг є доступный з $1.',
 'copyrightpage' => '{{ns:project}}:Авторьске право',
 'currentevents' => 'Актуалны подїї',
 'currentevents-url' => 'Project:Актуалны подїї',
-'disclaimers' => 'Ð\92Ñ\8bлÑ\83Ñ\87Ñ\96нÑ\8f Ð·Ð¾Ð´Ð¿Ð¾Ð²Ñ\96дноÑ\81Ñ\82и',
-'disclaimerpage' => 'Project:Ð\92Ñ\8bлÑ\83Ñ\87Ñ\96нÑ\8f Ð·Ð¾Ð´Ð¿Ð¾Ð²Ñ\96дноÑ\81Ñ\82и',
-'edithelp' => 'Ð\9fомÑ\96Ñ\87 Ð¿Ñ\80о ÐµÐ´Ñ\96Ñ\82ованÑ\8f',
+'disclaimers' => 'Вылучіня одповідности',
+'disclaimerpage' => 'Project:Вылучіня одповідности',
+'edithelp' => 'Поміч едітованя',
 'edithelppage' => 'Help:Едітованя',
 'helppage' => 'Help:Обсяг',
 'mainpage' => 'Головна сторінка',
@@ -292,12 +292,12 @@ $1',
 'policy-url' => 'Project:Правила',
 'portal' => 'Портал комуніты',
 'portal-url' => 'Project:Портал комуніты',
-'privacy' => 'Політіка охороны особных дат',
-'privacypage' => 'Project:Ð\9eÑ\85оÑ\80она Ð¾Ñ\81обнÑ\8bÑ\85 Ð´Ð°Ñ\82',
+'privacy' => 'Політіка сокочіня пріватных дан',
+'privacypage' => 'Project:СокоÑ\87Ñ\96нÑ\8f Ð¿Ñ\80Ñ\96ваÑ\82нÑ\8bÑ\85 Ð´Ð°Ð½',
 
-'badaccess' => 'Ð¥Ñ\8bба Ð¾Ð¿Ñ\80авнÑ\97нÑ\8f',
-'badaccess-group0' => 'Ð\92ам Ð½Ðµ Ñ\94 Ð¿Ð¾волено выконавати тоту дїю.',
-'badaccess-groups' => 'Ð\94Ñ\97Ñ\8f, Ñ\8fкÑ\83 Ñ\81Ñ\8cÑ\82е Ñ\85оÑ\82Ñ\97ли Ð·Ñ\80обиÑ\82и, Ð¿Ð¾волена лем хоснователям із {{PLURAL:$2|ґрупы|ґруп}}: $1.',
+'badaccess' => 'Ð\91Ñ\80ак Ð¿Ñ\80ав Ð¿Ñ\80иÑ\81Ñ\82Ñ\83пÑ\83',
+'badaccess-group0' => 'Ð\92ам Ð½Ðµ Ñ\94 Ð´Ð¾Ð·волено выконавати тоту дїю.',
+'badaccess-groups' => 'Ð\94Ñ\97Ñ\8f, Ñ\8fкÑ\83 Ñ\81Ñ\8cÑ\82е Ñ\85оÑ\82Ñ\97ли Ð·Ñ\80обиÑ\82и, Ð´Ð¾Ð·волена лем хоснователям із {{PLURAL:$2|ґрупы|ґруп}}: $1.',
 
 'versionrequired' => 'Потрібна MediaWiki верзії $1',
 'versionrequiredtext' => 'Про роботу з тов сторінков потрібна MediaWiki верзії $1. Відь [[Special:Version|сторінку верзії]].',
@@ -307,10 +307,14 @@ $1',
 'youhavenewmessages' => 'Маєте $1 ($2).',
 'newmessageslink' => 'новы повідомлїня',
 'newmessagesdifflink' => 'послїдня зміна',
+'youhavenewmessagesfromusers' => 'Мате $1 од {{PLURAL:$3|іншого хоснователя|$3 іншых хоснователїв}} ($2).',
+'youhavenewmessagesmanyusers' => 'Мате $1 од много далшых хоснователїв ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|ное повідомлїня|новы повідомлїня}}',
+'newmessagesdifflinkplural' => 'остатня {{PLURAL:$1|зміна|зміны|змін}}',
 'youhavenewmessagesmulti' => 'Маєте новы ознамы на $1',
 'editsection' => 'едіт.',
 'editold' => 'едіт.',
-'viewsourceold' => 'відїти код',
+'viewsourceold' => 'видїти код',
 'editlink' => 'едітовати',
 'viewsourcelink' => 'видїти код',
 'editsectionhint' => 'Едітовати секцію: $1',
@@ -329,9 +333,9 @@ $1',
 'site-atom-feed' => '$1 Atom канал',
 'page-rss-feed' => '"$1" RSS канал',
 'page-atom-feed' => '"$1" Atom канал',
-'red-link-title' => '$1 (такой сторінкы не є)',
-'sort-descending' => 'Сортовати зоступно',
-'sort-ascending' => 'Сортовати взоступно',
+'red-link-title' => '$1 (такой сторінкы нїт)',
+'sort-descending' => 'Сортовати спадаючі',
+'sort-ascending' => 'Сортовати ступаючі',
 
 # Short words for each namespace, by default used in the namespace tab in monobook
 'nstab-main' => 'Сторінка',
@@ -349,7 +353,7 @@ $1',
 'nosuchaction' => 'Такой дїї не має',
 'nosuchactiontext' => 'Дїя, уведжена в URL, неправилна.
 Могли сьте неправилно написати URL або перейти через некоректный одказ .
-Може тыж значіти хыбу в програмовім забезпечіню {{GRAMMAR:genitive|{{SITENAME}}}}.',
+Може тыж значіти ґанч в проґрамовім забеспечіню {{GRAMMAR:genitive|{{SITENAME}}}}.',
 'nosuchspecialpage' => 'Такой шпеціалной сторінкы нїт',
 'nospecialpagetext' => '<strong>Така шпеціална сторінка не екзістує.</strong>
 
@@ -359,7 +363,7 @@ $1',
 'error' => 'Хыба',
 'databaseerror' => 'Датабазова хыба',
 'dberrortext' => 'Найджена  сінтактічна хыба в запросї до датабазы.
-Тото може вказовати на хыбу в проґрамовім забезпечіню.
+Тото може вказовати на хыбу в проґрамовім забеспечіню.
 Послїднїй запрос до датабазы:
 <blockquote><tt>$1</tt></blockquote>
 з функції "<tt>$2</tt>".
@@ -386,8 +390,8 @@ $1',
 'readonly_lag' => 'Датабаза автоматічно заблокована од змін, докы ся другый датабазовый сервер не сінхронізує з мастером',
 'internalerror' => 'Інтерна хыба',
 'internalerror_info' => 'Інтерна хыба: $1',
-'fileappenderrorread' => 'Ð\92 Ñ\87аÑ\81Ñ\97 Ð¿Ñ\80идаванÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило прочітати  "$1".',
-'fileappenderror' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся придати «$1» до «$2».',
+'fileappenderrorread' => 'Ð\92 Ñ\87аÑ\81Ñ\97 Ð¿Ñ\80идаванÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°ло прочітати  "$1".',
+'fileappenderror' => 'Ð\9dе Ð²Ð´Ð°ло ся придати «$1» до «$2».',
 'filecopyerror' => 'Не было можне копіровати файл «$1» на «$2».',
 'filerenameerror' => 'Не было можне переменовати файл «$1» на «$2».',
 'filedeleteerror' => 'Не было можне змазаты файл «$1».',
@@ -410,14 +414,14 @@ $1',
 'wrong_wfQuery_params' => 'Неправилны параметры функцій wfQuery()<br />
 Функція: $1<br />
 Запрос: $2',
-'viewsource' => 'Відїти код',
-'viewsource-title' => 'Відїти жрідло сторінкы $1',
+'viewsource' => 'Видїти код',
+'viewsource-title' => 'Видїти жрідло сторінкы $1',
 'actionthrottled' => 'Акція была придушена',
 'actionthrottledtext' => 'Взглядом ку протиспамовым опатрїням не можете жадану акцію провести барз часто в короткім часї. Спробуйте то знову о пару мінут.',
 'protectedpagetext' => 'Тота сторінка была замкнута, также ся не дасть едітовати',
 'viewsourcetext' => 'Можете видїти і копіровати код той сторінкы:',
 'viewyourtext' => "Можете собі посмотрити і скопіровати жрідловый текст '''вашых змін''' той сторінкы:",
-'protectedinterface' => 'Тота сторінка є частинов інтрефейсу проґрамового забезпечіня і єй можуть едітовати лем адміністраторы проєкту.',
+'protectedinterface' => 'Тота сторінка є частёв інтрефейсу проґрамового забеспечіня і єй можуть едітовати лем адміністраторы проєкту.',
 'editinginterface' => "'''Увага:''' Едітуєте сторінку,котра є частинов текстового інтерфейсу. Зміны той сторінкы выкличуть зміну інтерфейсу про іншых хоснователїв. Про переклад увідомлїня хоснуйте [//translatewiki.net/wiki/Main_Page?setlang=uk translatewiki.net] — проєкт, што ся занимає локалізаціёв MediaWiki.",
 'sqlhidden' => '(SQL запрос скрытый)',
 'cascadeprotected' => 'Сторінка є замнкута, бо є вложена до  {{PLURAL:$1|наслїдуючой сторінкы замкнуты|наслїдуючіх сторінок замнкнутых|наслїдуючіх сторінок замнкнутых}} каскадовым замком:
@@ -437,7 +441,7 @@ $2',
 
 # Virus scanner
 'virus-badscanner' => "Зла конфіґурація: незнамый антивіровый проґрам: ''$1''",
-'virus-scanfailed' => 'Ñ\81кенованÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило (код $1)',
+'virus-scanfailed' => 'Ñ\81кенованÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°ло (код $1)',
 'virus-unknownscanner' => 'незнамый антівірус',
 
 # Login and logout pages
@@ -447,13 +451,14 @@ $2',
 'welcomecreation' => '== Вітаєме вас, $1! ==
 Ваше конто было вытворене.
 Не забудьте змінити свої [[Special:Preferences|наставлїня сайту]].',
-'yourname' => 'Ð\9cено хоснователя:',
+'yourname' => 'Ð\86мÑ\8f хоснователя:',
 'yourpassword' => 'Гесло:',
 'yourpasswordagain' => 'Повторяйте гесло:',
 'remembermypassword' => 'Запамятати моє приголошіня на тім компютерї (максімално $1 {{PLURAL:$1|день|днів}})',
 'securelogin-stick-https' => 'Останьте  припоєны через HTTPS по приголошіню',
 'yourdomainname' => 'Ваша домена:',
-'externaldberror' => 'Або ся стала хыба екстерной автентіфікачной датабазы, або не маєте поволено мінити своє екстерне конто.',
+'password-change-forbidden' => 'На тій вікі не можете мінити гесла.',
+'externaldberror' => 'Або ся стала хыба екстерной автентіфікачной датабазы, або не маєте дозволено мінити своє екстерне конто.',
 'login' => 'Приголошіня',
 'nav-login-createaccount' => 'Приголошіня / створїня конта',
 'loginprompt' => 'К приголошіню до {{grammar:2sg|{{SITENAME}}}} мусите мати актівованы cookies.',
@@ -467,20 +472,20 @@ $2',
 'createaccount' => 'Вытворити конто',
 'gotaccount' => "Уж сьте реґістрованы? '''$1'''.",
 'gotaccountlink' => 'Приголошіня',
-'userlogin-resetlink' => 'Забыли сьте вашы дата про приголошіня?',
+'userlogin-resetlink' => 'Забыли сьте вашы даны на приголошіня?',
 'createaccountmail' => 'електроничнов поштов',
 'createaccountreason' => 'Причіна:',
 'badretype' => 'Вами написаны гесла не сугласять.',
 'userexists' => 'Уведжене імя хоснователя ся уж хоснує.
 Просиме, выберьте інше імя.',
 'loginerror' => 'Хыба при приголошованю',
-'createaccounterror' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся створити конто хоснователя: $1',
+'createaccounterror' => 'Ð\9dе Ð²Ð´Ð°ло ся створити конто хоснователя: $1',
 'nocookiesnew' => 'Конто хоснователя было створене, але не сьте приголошены.
 {{SITENAME}} хоснує cookies про приголошіня але вы маєте cookies выпнуты .
 Просиме Вас, повольте їх, а потім ся приголоште знову з вашым новым меном і геслом.',
 'nocookieslogin' => '{{SITENAME}} хоснує cookies про приголошіня хоснователїв. Вы маєте cookies выпнуты. Просиме Вас, повольте їх і спобуйте знова.',
-'nocookiesfornew' => 'Конто хоснователя не было створене, бо сьме не были годны підтветдити ёго походжіня.
-УÑ\82веÑ\80диÑ\82е Ñ\81Ñ\8f, Ð¶Ðµ Ð¼Ð°Ñ\94Ñ\82е Ð¿Ð¾волены cookies, обновте тоту сторінку і спробуйте то знову.',
+'nocookiesfornew' => 'Конто хоснователя не было створене, бо сьме не были годны потвердити ёго походжіня.
+УÑ\82веÑ\80диÑ\82е Ñ\81Ñ\8f, Ð¶Ðµ Ð¼Ð°Ñ\94Ñ\82е Ð´Ð¾Ð·волены cookies, обновте тоту сторінку і спробуйте то знову.',
 'noname' => 'Мусите увести мено свого конта.',
 'loginsuccesstitle' => 'Успішне приголошіня',
 'loginsuccess' => "'''Теперь працуєте {{grammar:locative|{{SITENAME}}}} під меном $1.'''",
@@ -488,7 +493,7 @@ $2',
 'nosuchusershort' => 'Хоснователь з меном $1 не екзістує.
 Перевірте правилность написаня мена.',
 'nouserspecified' => 'Мусите задати мено хоснователя.',
-'login-userblocked' => 'Тот хоснователь є заблокованый. Приголошіня не є доволене.',
+'login-userblocked' => 'ТоÑ\82 Ñ\85оÑ\81новаÑ\82елÑ\8c Ñ\94 Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ñ\8bй. Ð\9fÑ\80иголоÑ\88Ñ\96нÑ\8f Ð½Ðµ Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ðµ.',
 'wrongpassword' => 'Задали сьте хыбне гесло. Спробуйте іщі раз.',
 'wrongpasswordempty' => 'Было задане порожнє гесло. Спробуйте іщі раз.',
 'passwordtooshort' => 'Гесло мусить быти довге холем  $1 {{PLURAL:$1|знак|знакы|знаків}}.',
@@ -509,16 +514,16 @@ $2',
 'noemailcreate' => 'Мусите задати правилну адресу електронічной пошты',
 'passwordsent' => 'Нове гесло было послане на адресу електронічной пошты реґістровану про „$1“. Приголосьте ся зясь, кідь го обтримете.',
 'blocked-mailpassword' => 'Вашій IP адресї была заблокована можность едітації і сучасно з тым є заблокована функція про засланя нового гесла.',
-'eauthentsent' => 'На задану адресу електронічной пошты было послане підтверджіня,
-Передтым як вам на тоту адресу буде мочі быти засылана далша пошта, наслїдуйте інштрукції в ел. пошті, жебы сьте підтвердили, же тота адреса справды належыть вам.',
-'throttled-mailpassword' => 'Ð\93еÑ\81ло Ñ\83ж Ð±Ñ\8bло Ñ\80аз Ð·Ð°Ñ\81лане Ð¿Ð¾Ñ\87аÑ\81 уплынулых $1 годин.
+'eauthentsent' => 'На задану адресу електронічной пошты было послане потверджіня,
+Передтым як вам на тоту адресу буде мочі быти засылана далша пошта, слїдуйте інштрукції в ел. пошті, жебы сьте потвердили, же тота адреса справды належыть вам.',
+'throttled-mailpassword' => 'Ð\93еÑ\81ло Ñ\83ж Ð±Ñ\8bло Ñ\80аз Ð·Ð°Ñ\81лане Ð±Ñ\96гом уплынулых $1 годин.
 Гесло може быти заслане лем раз за $1 {{PLURAL:$1|годину|годины|годин}}.',
 'mailerror' => 'Хыба засыланя ел. пошты: $1',
-'acct_creation_throttle_hit' => 'ХоÑ\81новаÑ\82елÑ\97 Ð¿Ñ\80иÑ\85одÑ\8fÑ\87Ñ\96 Ð· Ð²Ð°Ñ\88ой IP Ð°Ð´Ñ\80еÑ\81Ñ\8b Ñ\83ж Ð´Ð½ÐµÑ\81Ñ\8c Ñ\81Ñ\82воÑ\80или {{PLURAL:$1|конÑ\82о|конÑ\82а|конÑ\82}}, Ñ\88Ñ\82о Ñ\94 Ð´Ð¾Ð²Ð¾Ð»ÐµÐ½Ðµ Ð¼Ð°ÐºÑ\81Ñ\96мÑ\83м. Ð\97аÑ\82о Ñ\82епеÑ\80Ñ\8c Ð½Ðµ Ñ\94 Ð´Ð¾волено з той IP адресы закладати далшы конта.',
+'acct_creation_throttle_hit' => 'ХоÑ\81новаÑ\82елÑ\97 Ð¿Ñ\80иÑ\85одÑ\8fÑ\87Ñ\96 Ð· Ð²Ð°Ñ\88ой IP Ð°Ð´Ñ\80еÑ\81Ñ\8b Ñ\83ж Ð´Ð½ÐµÑ\81Ñ\8c Ñ\81Ñ\82воÑ\80или {{PLURAL:$1|конÑ\82о|конÑ\82а|конÑ\82}}, Ñ\88Ñ\82о Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ðµ Ð¼Ð°ÐºÑ\81Ñ\96мÑ\83м. Ð\97аÑ\82о Ñ\82епеÑ\80Ñ\8c Ð½Ðµ Ñ\94 Ð´Ð¾Ð·волено з той IP адресы закладати далшы конта.',
 'emailauthenticated' => 'Адреса вашой ел. пошты была овірена дня $2 о $3.',
 'emailnotauthenticated' => 'Адреса вашой ел. пошты дотеперь не была овірена, функції ел. пошты суть недоступны.',
 'noemailprefs' => 'Шпеціфікуйте адресу ел. пошты, жебы наслїднуючі можности могли фунґовати.',
-'emailconfirmlink' => 'Підтвердьте свою адресу ел. пошты',
+'emailconfirmlink' => 'Потвердьте свою адресу ел. пошты',
 'invalidemailaddress' => 'Уведена адреса ел. пошты не може быти прията, бо она не має правилный формат.
 Просиме Вас, уведьте коректну адесу або зохабте поле порожнє.',
 'cannotchangeemail' => 'В тій вікі не годен мінити імейловы адресы.',
@@ -532,7 +537,7 @@ $2',
 'usernamehasherror' => 'Мено хоснователя не сміє обсяговати сімбол мережка (#)',
 'login-throttled' => 'Зробили сьте дуже много спроб о приголошіня.
 Просиме Вас, почекайте перед далшов спробов.',
-'login-abort-generic' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся войти до сістемы.',
+'login-abort-generic' => 'Ð\9dе Ð²Ð´Ð°ло ся войти до сістемы.',
 'loginlanguagelabel' => 'Язык: $1',
 'suspicious-userlogout' => 'Ваша пожадавка на одголошіня была одвергнута, бо вызерає то так, же была послана розбитым переглядачом або кешуючім проксі-сервером.',
 
@@ -591,7 +596,7 @@ $2
 Дочасне гесло: $2',
 'passwordreset-emailsent' => 'Імейл з геслом быв посланый.',
 'passwordreset-emailsent-capture' => 'Быв выґенерованый припоминаючій імейл, котрый є указаный ниже.',
-'passwordreset-emailerror-capture' => 'Ð\91Ñ\8bв Ð²Ñ\8bÒ\91енеÑ\80ованÑ\8bй Ð¿Ñ\80ипоминаÑ\8eÑ\87Ñ\96й Ñ\96мейл, ÐºÐ¾Ñ\82Ñ\80Ñ\8bй Ñ\94 Ñ\83казанÑ\8bй Ð½Ð¸Ð¶Ðµ, Ð°Ð»Ðµ Ð¾Ð´Ð¾Ñ\81лаÑ\82и Ñ\85оÑ\81новаÑ\82елÑ\91ви Ñ\81Ñ\8f Ð³Ð¾ Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило: $1',
+'passwordreset-emailerror-capture' => 'Ð\91Ñ\8bв Ð²Ñ\8bÒ\91енеÑ\80ованÑ\8bй Ð¿Ñ\80ипоминаÑ\8eÑ\87Ñ\96й Ñ\96мейл, ÐºÐ¾Ñ\82Ñ\80Ñ\8bй Ñ\94 Ñ\83казанÑ\8bй Ð½Ð¸Ð¶Ðµ, Ð°Ð»Ðµ Ð¾Ð´Ð¾Ñ\81лаÑ\82и Ñ\85оÑ\81новаÑ\82елÑ\91ви Ñ\81Ñ\8f Ð³Ð¾ Ð½Ðµ Ð²Ð´Ð°ло: $1',
 
 # Special:ChangeEmail
 'changeemail' => 'Зміна імейловой адресы',
@@ -669,8 +674,8 @@ $2
 Просиме Вас, пиште тоты детайлы во вшыткых запытах на адміністратора.",
 'blockednoreason' => 'причіна не была задана',
 'whitelistedittext' => 'Про едітованя ся мусите $1.',
-'confirmedittext' => 'Мусите підтвердити вашу адресу ел. пошты передтым як будете едітовати сторінкы.
-На сторінцї [[Special:Preferences|наставлїня]] задайте і зохабте собі підтвердити свою адресу ел. пошты.',
+'confirmedittext' => 'Мусите потвердити вашу адресу ел. пошты передтым як будете едітовати сторінкы.
+На сторінцї [[Special:Preferences|наставлїня]] задайте і зохабте собі потвердити свою адресу ел. пошты.',
 'nosuchsectiontitle' => 'Секція ненайдена',
 'nosuchsectiontext' => 'Пробуєте едітовати секцію, котра не екзістує.
 Може была переменована або змазана, покы сьте перезерали сторінку.',
@@ -678,8 +683,8 @@ $2
 'loginreqlink' => 'Приголосити ся',
 'loginreqpagetext' => 'Ку перезераню іншых сторінок ся мусите $1.',
 'accmailtitle' => 'Гесло одослане.',
-'accmailtext' => 'Ð\9dагодно выґенероване гесло про хоснователя [[User talk:$1|$1]] было послане на $2.
-Гесло того нового конта буде можне по приголошіню змінити на [[Special:ChangePassword|сторінцї про зміну гесла]].',
+'accmailtext' => 'ТÑ\80аÑ\84Ñ\83нково выґенероване гесло про хоснователя [[User talk:$1|$1]] было послане на $2.
+Гесло того нового конта буде мочі по приголошіню змінити на [[Special:ChangePassword|сторінцї про зміну гесла]].',
 'newarticle' => '(Нова)',
 'newarticletext' => "Перешли сте на сторінку, котра іщі не екзістує.
 Нову сторінку створите так, же зачнете писати в окнї ниже (вид. [[{{MediaWiki:Helppage}}|сторінка помочі]], про вецей інформації).
@@ -692,11 +697,15 @@ $2
 'noarticletext-nopermission' => 'Теперь на тїй сторінцї не є текст.
 Можете [[Special:Search/{{PAGENAME}}|глядати тоту назву]] в іншых сторінках,
 або <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} глядати в лоґах]</span>.',
+'missing-revision' => 'Ревізія #$1 сторінкы з назвов „{{PAGENAME}}“ не є.
+
+Гевсе звычайно запрічінене так, же наслїдовали сьте застарїлый історічный одказ на сторінку, котра была уж змазана.
+Детайлы можуть быти найджены в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} книзї змазаных сторінок].',
 'userpage-userdoesnotexist' => 'Хосновательске конто під назвов  "<nowiki>$1</nowiki>" не є реґістроване. Сконтролюйте ці хочете вытворити/едітовати тоту сторінку.',
 'userpage-userdoesnotexist-view' => 'Хосновательске конто „$1 не є реґістроване.',
 'blocked-notice-logextract' => 'Тот хоснователь є теперь блокованый.
 Послїднїй запис в лоґах блоковань є такый:',
-'clearyourcache' => "'''Позначка: По уложіню мусите вымазати кеш вашого перезерача, інакше зміны не будете відїти.'''
+'clearyourcache' => "'''Позначка: По уложіню мусите вымазати кеш вашого перезерача, інакше зміны не будете видїти.'''
 '''Mozilla / Firefox / Safari:''' При кликнутю на ''Актуалізовати'' тримайте ''Shift'', або стиснийте ''Ctrl-F5'' або ''Ctrl-R'' (на Macintosh ''Command-R'');
 '''Konqueror''': Кликнийте на ''Актуалізовати'' або стиснийте ''F5'';
 '''Opera:''' Вымажте обсяг кеш в меню ''Інштрументы → Наставлїня'';
@@ -717,14 +726,14 @@ $2
 Зміны іщі не суть уложены!",
 'continue-editing' => 'Продовжыти едітованя',
 'previewconflict' => 'Тот нагляд зображує текст так, як буде вызерати по уложіню сторінкы.',
-'session_fail_preview' => "'''Вашу пожадавку ся не подарило зпрацовати, бо были страчены дата сеансу.
+'session_fail_preview' => "'''Вашу пожадавку ся не удало спрацовати, бо были страчены дата сеансу.
 Просиме, спробуйте то зясь.
\9aÑ\96дÑ\8c Ñ\81Ñ\8f Ñ\82оÑ\82 Ð¿Ñ\80облем Ð±Ñ\83де Ð¾Ð¿Ð°ÐºÐ¾Ð²Ð°Ñ\82и, Ñ\81пÑ\80обÑ\83йÑ\82е Ñ\81Ñ\8f [[Special:UserLogout|одголоÑ\81иÑ\82и]] Ñ\96 Ð·Ð½Ð¾Ð²Ñ\83 Ð¿Ñ\80иголоÑ\81иÑ\82и Ð´Ð¾ Ñ\81Ñ\96Ñ\81Ñ\82емÑ\83.'''",
-'session_fail_preview_html' => "'''Вашу пожадавку ся не подарило зпрацовати, бо были страчены дата сеансу..'''
\9aÑ\96дÑ\8c Ñ\81Ñ\8f Ñ\82оÑ\82 Ð¿Ñ\80облем Ð±Ñ\83де Ð¾Ð¿Ð°ÐºÐ¾Ð²Ð°Ñ\82и, Ñ\81пÑ\80обÑ\83йÑ\82е Ñ\81Ñ\8f [[Special:UserLogout|одголоÑ\81иÑ\82и]] Ñ\96 Ð·Ð½Ð¾Ð²Ñ\83 Ð¿Ñ\80иголоÑ\81иÑ\82и Ð´Ð¾ Ñ\81Ñ\96Ñ\81Ñ\82емÑ\8b.'''",
+'session_fail_preview_html' => "'''Вашу пожадавку ся не удало спрацовати, бо были страчены дата сеансу..'''
 
-''Зато же {{SITENAME}} має запнуте хоснованя чістого HTML, нагляд ся про превенцію проти утокам JavaScript-ом не зображує.''
+''Зато же {{SITENAME}} має запнуте хоснованя чістого HTML, нагляд ся про превенцію проти атакам JavaScript-ом не зображує.''
 
-'''Ð\9aÑ\96дÑ\8c Ñ\96де Ð¾ Ñ\80Ñ\8fднÑ\83 ÐµÐ´Ñ\96Ñ\82аÑ\86Ñ\96Ñ\8e, Ñ\81пÑ\80обÑ\83йÑ\82е Ñ\82о Ð·Ð½Ð¾Ð²Ñ\83. Ð\9aÑ\96дÑ\8c Ñ\81Ñ\8f Ñ\82оÑ\82 Ð¿Ñ\80облем Ð±Ñ\83де Ð¾Ð¿Ð°ÐºÐ¾Ð²Ð°Ñ\82и, Ñ\81пÑ\80обÑ\83йÑ\82е Ñ\81Ñ\8f [[Special:UserLogout|одголоÑ\81иÑ\82и]] Ñ\96 Ð·Ð½Ð¾Ð²Ñ\83 Ð¿Ñ\80иголоÑ\81иÑ\82и Ð´Ð¾ Ñ\81Ñ\96Ñ\81Ñ\82емÑ\83.'''",
+'''Ð\9aÑ\96дÑ\8c Ñ\96де Ð¾ Ñ\80Ñ\8fднÑ\83 ÐµÐ´Ñ\96Ñ\82аÑ\86Ñ\96Ñ\8e, Ñ\81пÑ\80обÑ\83йÑ\82е Ñ\82о Ð·Ð½Ð¾Ð²Ñ\83. Ð\9aÑ\96дÑ\8c Ñ\81Ñ\8f Ñ\82оÑ\82 Ð¿Ñ\80облем Ð±Ñ\83де Ð¿Ð¾Ð²Ñ\82оÑ\80иÑ\82и, Ñ\81пÑ\80обÑ\83йÑ\82е Ñ\81Ñ\8f [[Special:UserLogout|одголоÑ\81иÑ\82и]] Ñ\96 Ð·Ð½Ð¾Ð²Ñ\83 Ð¿Ñ\80иголоÑ\81иÑ\82и Ð´Ð¾ Ñ\81Ñ\96Ñ\81Ñ\82емÑ\8b.'''",
 'token_suffix_mismatch' => "'''Ваша едітація не была схвалена, бо ваш вебовый переглядач комолить дакотры знакы в едітованім текстї.
 Едітація не была схвалена, жебы ся заборонило пошкоджіню тексту сторінкы.
 Тото ся може даколи стати, кідь хоснуєте хыбный вебовый анонімізер.'''",
@@ -741,16 +750,16 @@ $2
 '''Лем''' выше вказаный текст зістане ухованый по кликнутю на  „{{int:savearticle}}“.",
 'yourtext' => 'Ваш текст',
 'storedversion' => 'Уложена верзія',
-'nonunicodebrowser' => "'''Увага: Ваш переглядач не є способный працовати із знаками Unicode. Абы сьте могли тоту сторінку безпечно едітовати: вшыткы знакы мімо  ASCII суть зображены в гексадецімалных кодах.'''",
+'nonunicodebrowser' => "'''Увага: Ваш переглядач не є способный працовати із знаками Unicode. Абы сьте могли тоту сторінку беспечно едітовати: вшыткы знакы мімо  ASCII суть зображены в гексадецімалных кодах.'''",
 'editingold' => "'''Увага: Нынї едітуєте застаралу верзію той сторінкы. Кідь єй уложыте, вшыткы пізнїшы зміны ся стратять.'''",
 'yourdiff' => 'Роздїлы',
-'copyrightwarning' => "Просиме Вас, уважте, што вшыткы додаваня і зміны до {{grammar:genitive|{{SITENAME}}}} будуть выпущены під ліценціёв $2 (від. $1).
+'copyrightwarning' => "Просиме Вас, уважте, што вшыткы додаваня і зміны до {{grammar:genitive|{{SITENAME}}}} будуть выпущены під ліценціов $2 (від. $1).
 Кідь не хочете, жебы написане вами ся немилосердно едітовало і шырило, пак ту не пиште.<br />
-Вы тыж підтверджуєте, што написане вами ту належыть вам або взяте із жрідла, што є  верейным ці вольным жрідлом.
-'''Ð\9dÐ\95 Ð\9fУÐ\91Ð\9bÐ\86Ð\9aУÐ\99ТÐ\95 Ð¢Ð£ Ð\91Ð\95Ð\97 Ð\9fÐ\9eÐ\92Ð\9eÐ\9bÐ\87Ð\9dЯ Ð\9cÐ\90ТÐ\95РÐ\86Ð\90Ð\9bЫ, Ð¨Ð¢Ð\9e Ð¡Ð¯ Ð\9eХРÐ\90Ð\9dЮЮТЬ АВТОРЬСКЫМ ПРАВОМ!''",
+Вы тыж потверджуєте, што написане вами ту належыть вам або взяте із жрідла, што є  публічным ці вольным жрідлом.
+'''Ð\9dÐ\95 Ð\9fУÐ\91Ð\9bÐ\86Ð\9aУÐ\99ТÐ\95 Ð¢Ð£ Ð\91Ð\95Ð\97 Ð\94Ð\9eÐ\97Ð\92Ð\9eÐ\9bÐ\87Ð\9dЯ Ð\9cÐ\90ТÐ\95РÐ\86Ð\90Ð\9bЫ, Ð¨Ð¢Ð\9e Ð¡Ð¯ Ð¡Ð\9eÐ\9aÐ\9eТЯТЬ АВТОРЬСКЫМ ПРАВОМ!''",
 'copyrightwarning2' => "Просиме Вас, уважте, што вшыткы додаваня і зміны до  {{grammar:2sg|{{SITENAME}}}} можуть быти другыма хоснователями управлены, змінены ці одстранены. Покы собі не желате, жебы ваш текст быв немилосердно управляный, пак го до {{grammar:2sg|{{SITENAME}}}} не укладайте.<br />
 Уложінём приспевку ся завязуєте, же є вашым дїлом або є скопірованый із жрідел, котры не суть хоронены авторьскым правом (тзв. <em>public domain</em>), детайлы найдете на $1. '''Не копіруйте дїла хоронены авторьскым правом без дозволїня!'''",
-'longpageerror' => "'''ХЫÐ\91Ð\90: Ð\9fÑ\80обÑ\83Ñ\94Ñ\82е Ñ\83ложÑ\8bÑ\82и Ñ\82екÑ\81Ñ\82 Ð¾ Ð²ÐµÐ»Ð¸ÐºÐ¾Ñ\81Ñ\82и {{PLURAL:$1|$1 Ð\9aÑ\96Ð\91}}, Ð¿Ñ\80иÑ\87Ñ\96м Ð´Ð¾волене максімум є {{PLURAL:$2|$2 КіБ}}. Ваша едітація не може быти уложена.'''",
+'longpageerror' => "'''ХЫÐ\91Ð\90: Ð\9fÑ\80обÑ\83Ñ\94Ñ\82е Ñ\83ложÑ\8bÑ\82и Ñ\82екÑ\81Ñ\82 Ð¾ Ð²ÐµÐ»Ð¸ÐºÐ¾Ñ\81Ñ\82и {{PLURAL:$1|$1 Ð\9aÑ\96Ð\91}}, Ð°Ð»Ðµ Ð´Ð¾Ð·волене максімум є {{PLURAL:$2|$2 КіБ}}. Ваша едітація не може быти уложена.'''",
 'readonlywarning' => "'''УВАГА: Датабаза была замкнута про утримованя, также не будете мочі уложыти свої зміны. Можете сі го окопіровати до файлу і уложыти го пізнїше.'''
 
 Адміністратор сервера, котрый датабазу замкнув, зохабив тото пояснїня: $1",
@@ -764,8 +773,8 @@ $2
 'templatesused' => '{{PLURAL:$1|Шаблона, хоснована|Шаблоны, хоснованы}} на тій сторінці:',
 'templatesusedpreview' => '{{PLURAL:$1|Шаблона, хоснована|Шаблоны, хоснованы}} у тім перегляді:',
 'templatesusedsection' => '{{PLURAL:$1|Шаблона, хоснована|Шаблоны, хоснованы}} у тій секції:',
-'template-protected' => '(хранена)',
-'template-semiprotected' => '(частково хранено)',
+'template-protected' => '(всокочена)',
+'template-semiprotected' => '(частково всокочене)',
 'hiddencategories' => 'Тота сторінка належыть до $1 {{PLURAL:$1|схованa катеґорія|схованы катеґорії|схованых катеґорій}}:',
 'nocreatetitle' => 'Створїня сторінок обмеджено',
 'nocreatetext' => 'На {{grammar:6sg|{{SITENAME}}}} є можливость створїна новых сторінок обмеджена.
@@ -775,7 +784,7 @@ $2
 'sectioneditnotsupported-text' => 'На тій сторінцї не є підпороване едітованя єдной секції.',
 'permissionserrors' => 'Хыба оправнїня',
 'permissionserrorstext' => 'Не маєте поволїня той операції з  {{PLURAL:$1|такой причіны|такых причін}}:',
-'permissionserrorstext-withaction' => 'Ð\9dе Ð¼Ð°Ñ\94Ñ\82е Ð¿Ð¾Ð²Ð¾Ð»Ñ\97нÑ\8f Ð½Ð° $2 Ð· {{PLURAL:$1|Ñ\82акой Ð¿Ñ\80иÑ\87Ñ\96нÑ\8b\82акÑ\8bÑ\85 Ð¿Ñ\80ичін}}:',
+'permissionserrorstext-withaction' => 'Ð\9dе Ð¼Ð°Ñ\94Ñ\82е Ð´Ð¾Ð·Ð²Ð¾Ð»Ñ\97нÑ\8f Ð½Ð° $2 Ð· {{PLURAL:$1|Ñ\82акой Ð¿Ñ\80Ñ\96Ñ\87Ñ\96нÑ\8b\82акÑ\8bÑ\85 Ð¿Ñ\80Ñ\96чін}}:',
 'recreate-moveddeleted-warn' => "'''Увага: Пробуєте знову створити сторінку, котра была в минулости змазана.'''
 
 Уважте, ці справды треба знову створити тоту сторінку.
@@ -784,19 +793,18 @@ $2
 Запис змазаня а переменованя сі можете посмотрити ниже.',
 'log-fulllog' => 'Зобразити вшыток запис',
 'edit-hook-aborted' => 'Едітованя было сторноване процедуров без близшого пояснїня.',
-'edit-gone-missing' => 'СÑ\82оÑ\80Ñ\96нкÑ\83 Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило обновити.
+'edit-gone-missing' => 'СÑ\82оÑ\80Ñ\96нкÑ\83 Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°ло обновити.
 Асі была змазана.',
 'edit-conflict' => 'Конфлікт едітованя.',
-'edit-no-change' => 'Ваша едітація была іґнорована, бо не дішло ку жадній змінї тексту.',
-'edit-already-exists' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся створити нову сторінку, бо она уж екзістує.',
+'edit-no-change' => 'Ваша едітація была іґнорована, бо ся не зробила жадна зміна тексту.',
+'edit-already-exists' => 'Ð\9dе Ð²Ð´Ð°ло ся створити нову сторінку, бо она уж екзістує.',
 'defaultmessagetext' => 'Преднаставленый текст повідомлїня',
 
 # Parser/template warnings
-'expensive-parserfunction-warning' => 'Увага: Тота сторінка обсягує дуже много кликаня выконово нарочных функцій парсера.
-
-Поволеный ліміт є $2, теперь шак  {{PLURAL:$1|ту єдно кликаня є|ту суть  $2 кликаня|ту є $2 кликань}}.',
+'expensive-parserfunction-warning' => 'Увага: Тота сторінка обсягує дуже много кликаня выконово тяжкых функцій парсера.
+Дозволеный ліміт є $2, теперь шак  {{PLURAL:$1|ту єдно кликаня є|ту суть  $2 кликаня|ту є $2 кликань}}.',
 'expensive-parserfunction-category' => 'Сторінкы з дуже великым чіслом кликаня функції парсера',
-'post-expand-template-inclusion-warning' => 'Ð\9fозÑ\96Ñ\80Ñ\8c: Ñ\80озмÑ\96Ñ\80 Ñ\88аблон Ð¿Ñ\80о включіня є барз великый.
+'post-expand-template-inclusion-warning' => 'Ð\9fозÑ\96Ñ\80Ñ\8c: Ñ\80озмÑ\96Ñ\80 Ñ\88аблон Ð½Ð° включіня є барз великый.
 Дакотры шаблоны не будуть включены.',
 'post-expand-template-inclusion-category' => 'Сторінкы з перевышуючов великостёв включеных шаблон',
 'post-expand-template-argument-warning' => 'Увага: Тота сторінка обсягує принайменшім єден арґумент шаблоны, котрый є по роспакованю дуже великый.
@@ -811,6 +819,7 @@ $2
 'expansion-depth-exceeded-warning' => 'Сторінка перевышыла глубку експанзії',
 'parser-unstrip-loop-warning' => 'Выявлене заціклїня unstrip',
 'parser-unstrip-recursion-limit' => 'Перевышеный ліміт рекурзії unstrip ($1)',
+'converter-manual-rule-error' => 'Найджена хыба в ручнім правилї конверзії языка',
 
 # "Undo" feature
 'undo-success' => 'Едітованя може быти зручене.
@@ -903,7 +912,7 @@ $3 зазначів тоту причіну: ''$2''",
 'revdelete-text' => "'''Змазаны верзії і подїї будуть надале зображены в історії сторінкы і протоколовачіх записах, але дакотры їх части не будуть публікованы.'''
 Другы адміністраторы {{GRAMMAR:2sg|{{SITENAME}}}} собі будуть мочі схованый обсяг перезерати і помочов того самого інтерфейсу го будуть мочі обновити,
 кідь не были наставлены далшы обмеджіня.",
-'revdelete-confirm' => 'Просиме Вас, підтвердьте же то хочете справды зробити, же собі усвідомуєте резултат і же є то в згодї з  [[{{MediaWiki:Policy-url}}|правилами]].',
+'revdelete-confirm' => 'Просиме Вас, потвердьте, же то хочете справды зробити, же собі усвідомлюєте резултат і же є то в згодї з  [[{{MediaWiki:Policy-url}}|правилами]].',
 'revdelete-suppress-text' => "Затаёваня бы ся мало хосновати  ''лем''' в такых припадах:
 * Потенціално огварячі інформації
 * Непотрібны особны дата
@@ -923,10 +932,10 @@ $3 зазначів тоту причіну: ''$2''",
 'revdelete-log' => 'Причіна:',
 'revdelete-submit' => 'Апліковати на {{PLURAL:$1|зазначену ревізію|зазначены ревізії}}',
 'revdelete-success' => "'''Видимость ревізії успішно змінена.'''",
-'revdelete-failure' => "'''Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся змінити видимость ревізії:'''
+'revdelete-failure' => "'''Ð\9dе Ð²Ð´Ð°ло ся змінити видимость ревізії:'''
 $1",
 'logdelete-success' => "'''Видимость події успішно наставена.'''",
-'logdelete-failure' => "'''Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся наставити видимость протоколу.'''
+'logdelete-failure' => "'''Ð\9dе Ð²Ð´Ð°ло ся наставити видимость протоколу.'''
 $1",
 'revdel-restore' => 'Змінити видимость',
 'revdel-restore-deleted' => 'вымазаны ревізії',
@@ -960,8 +969,8 @@ $1",
 
 # History merging
 'mergehistory' => 'Злучованя історії сторінок',
-'mergehistory-header' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ð\92ам Ð´Ð¾Ð²Ð¾Ð»Ð¸Ñ\82Ñ\8c Ð·Ð»Ñ\83Ñ\87Ñ\96Ñ\82и Ñ\96Ñ\81Ñ\82оÑ\80Ñ\96Ñ\8e Ñ\94дной Ð·Ð´Ñ\80ойовой сторінкы з новшов сторінков.
-Пересвідчте ся, же тота зміна утриме цалосность і поступность  історії сторінкы.',
+'mergehistory-header' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ð\92ам Ð´Ð¾Ð·Ð²Ð¾Ð»Ð¸Ñ\82Ñ\8c Ð·Ð»Ñ\83Ñ\87Ñ\96Ñ\82и Ñ\96Ñ\81Ñ\82оÑ\80Ñ\96Ñ\8e Ñ\94дной Ð¶Ñ\80Ñ\96дловой сторінкы з новшов сторінков.
+Пересвіджте ся, же тота зміна утриме повязаность і поступность  історії сторінкы.',
 'mergehistory-box' => 'Злучіти ревізії двох сторінок:',
 'mergehistory-from' => 'Здроёва сторінка:',
 'mergehistory-into' => 'Цілёва сторінка:',
@@ -998,10 +1007,14 @@ $1",
 'editundo' => 'вернути назад',
 'diff-multi' => '({{PLURAL:$1|Не є зображена єдна міджілегла верзія|Не суть зображены $1 міджілеглы верзії|Не є зображено $1 міджілеглых верзій}} од {{PLURAL:$2|1 хоснователя|$2 хоснователїв}} .)',
 'diff-multi-manyusers' => '(Не є зображено $1 міджілеглых верзій од веце як $2 {{PLURAL:$2|хоснователя|хоснователїв}}.)',
+'difference-missing-revision' => '{{PLURAL:$2|Єдна з ревізій|$2 ревізії|$2 ревізій}} к пожадованому порівнаню ($1) {{PLURAL:$2|не є|не суть|не є}}.
+
+Гевсе є звычайно запрічінене так, же наслїдовали сьте застарїлый одказ історічнов ревізіов сторінкы, котра уж была змазана.
+Детайлы можуть быти найджены в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} книзї змазаный сторінок].',
 
 # Search results
 'searchresults' => 'Резултаты гляданя',
-'searchresults-title' => 'Резултаты гляданя про "$1"',
+'searchresults-title' => 'Резултаты гляданя "$1"',
 'searchresulttext' => 'Про детайлнїшы інформації про гляданя у проєктї, смоть [[{{MediaWiki:Helppage}}|сторінка помочі]]',
 'searchsubtitle' => 'Глядали сьте «[[:$1]]» ([[Special:Prefixindex/$1|вшыткы сторінкы, што ся зачінають на «$1»]]{{int:pipe-separator}}[[Special:WhatLinksHere/$1|вшыткы сторінкы, што мають одказ на «$1»]])',
 'searchsubtitleinvalid' => "Глядали сте '''$1'''",
@@ -1112,13 +1125,13 @@ $1",
 'resultsperpage' => 'Чісло резултатів на сторінку:',
 'stub-threshold' => 'Поріг про форматованя одказу як <a href="#" class="stub">одказів на "stub"</a> (v bajtech):',
 'stub-threshold-disabled' => 'Выпнуте',
-'recentchangesdays' => 'Ð\9dа кілько днїв вказовати новы едітованя',
+'recentchangesdays' => 'Ð\97а кілько днїв вказовати новы едітованя',
 'recentchangesdays-max' => '(максімум $1 {{PLURAL:$1|день|днї|днїв}})',
 'recentchangescount' => 'Чісло імпліцітно зображованых едітовань:',
 'prefs-help-recentchangescount' => 'Тыкать ся послїднїх змін, історії сторінок і протоколовачіх записів.',
 'prefs-help-watchlist-token' => 'Кідь до того поля выповните тайный ключ, буде створеный RSS канал вашых слїдованых сторінок.
-Хоцьхто хто знає тот ключ, пак буде мочі ваш список слїдованых сторінок чітати, также не забывайте на безпечность.
-Можете схосновати тоту нагодно выґенеровану годноту: $1',
+Хоцьхто хто знає тот ключ, пак буде мочі ваш список слїдованых сторінок чітати, та же не забывайте на беспечность.
+Можете схосновати тото трафунково выґенероване значіня: $1',
 'savedprefs' => 'Ваше наставлїня было уложене.',
 'timezonelegend' => 'Часова зона:',
 'localtime' => 'Містный час:',
@@ -1148,14 +1161,14 @@ $1",
 'prefs-common-css-js' => 'Сдїляне CSS/JS про вшыткы штілы:',
 'prefs-reset-intro' => 'Помочов той сторінкы можете вшыткы наставлїня вернути на імпліцітны годноты.
 Тоту операцію не годен вернути назад.',
-'prefs-emailconfirm-label' => 'Підтверджіня електронічной пошты:',
+'prefs-emailconfirm-label' => 'Потверджіня електронічной пошты:',
 'prefs-textboxsize' => 'Розмір окна едітованя',
 'youremail' => 'Адреса електронічной пошты:',
 'username' => 'Мено хоснователя:',
 'uid' => 'Ідентіфікатор хоснователя:',
 'prefs-memberingroups' => 'Член {{PLURAL:$1|ґрупы|ґруп}}:',
 'prefs-registration' => 'Час реґістрації:',
-'yourrealname' => 'СкÑ\83Ñ\82оÑ\87не Ð¼ÐµÐ½Ð¾:',
+'yourrealname' => 'Ð\9fÑ\80авдиве Ñ\96мÑ\8f:',
 'yourlanguage' => 'Язык:',
 'yourvariant' => 'Варіант языка обсягу:',
 'prefs-help-variant' => 'Вами преферованый варіант або правопис, як ся мають на тій вікі зображати обсяговы сторінкы.',
@@ -1219,7 +1232,7 @@ $1",
 # Groups
 'group' => 'Ґрупа:',
 'group-user' => 'Хоснователї',
-'group-autoconfirmed' => 'Автопідтверджены хоснователї',
+'group-autoconfirmed' => 'Автопотверджены хоснователї',
 'group-bot' => 'Боты',
 'group-sysop' => 'Адміністраторы',
 'group-bureaucrat' => 'Бірократы',
@@ -1234,7 +1247,7 @@ $1",
 'group-suppress-member' => '{{GENDER:$1|ревізор|ревізорка|ревізор}}',
 
 'grouppage-user' => '{{ns:project}}:Хоснователї',
-'grouppage-autoconfirmed' => '{{ns:project}}:Автопідтверджены хоснователї',
+'grouppage-autoconfirmed' => '{{ns:project}}:Автопотверджены хоснователї',
 'grouppage-bot' => '{{ns:project}}:Боты',
 'grouppage-sysop' => '{{ns:project}}:Адміністраторы',
 'grouppage-bureaucrat' => '{{ns:project}}:Бірократы',
@@ -1252,12 +1265,12 @@ $1",
 'right-move-rootuserpages' => 'Переменованя корінёвых сторінок хоснователїв',
 'right-movefile' => 'Переменовати файлы',
 'right-suppressredirect' => 'Нестворіня напрямлїня про переменоваю сторінкы',
-'right-upload' => 'Ð\9dагÑ\80аваня файлів',
+'right-upload' => 'Ð\97аладовованя файлів',
 'right-reupload' => 'Переписованя екзістуючіх файлів',
-'right-reupload-own' => 'Ð\9fеÑ\80епиÑ\81ованÑ\8f Ñ\84айлÑ\96в Ð½Ð°Ð³Ñ\80анÑ\8bÑ\85 Ñ\81ам Ñ\81обов',
-'right-reupload-shared' => 'Ð\9dагÑ\80аванÑ\8f Ð»Ð¾ÐºÐ°Ð»Ð½Ñ\8bÑ\85 Ñ\84айлÑ\96в Ð¿Ñ\80о Ð¿Ð¾Ñ\82иÑ\81нÑ\83Ñ\82Ñ\8f Ñ\82Ñ\8bÑ\85 Ð² Ñ\81дÑ\97лÑ\8fнÑ\96м Ñ\83Ñ\81Ñ\85овÑ\96щу',
-'right-upload_by_url' => 'Ð\9dагÑ\80аваня файлів з URL адрес',
-'right-purge' => 'Очіщіня кешу про сторінкы без підтверджовачого діалоґу',
+'right-reupload-own' => 'Ð\9fеÑ\80епиÑ\81ованÑ\8f Ñ\84айлÑ\96в Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8bÑ\85 Ð¾Ð´ Ñ\81ебе Ñ\81амого',
+'right-reupload-shared' => 'Ð\97аладованÑ\8f Ð»Ð¾ÐºÐ°Ð»Ð½Ñ\8bÑ\85 Ñ\84айлÑ\96в Ð¶ÐµÐ±Ñ\8b Ð¿ÐµÑ\80екÑ\80Ñ\8bли Ñ\82оÑ\82Ñ\8bÑ\85 Ð² Ñ\81полоÑ\87нÑ\96м Ñ\83Ñ\81Ñ\85овищу',
+'right-upload_by_url' => 'Ð\97аладовованя файлів з URL адрес',
+'right-purge' => 'Очіщіня кешу про сторінкы без потверджовачого діалоґу',
 'right-autoconfirmed' => 'Едітованя часточно замкнутых сторінок',
 'right-bot' => 'Быти поважованый за автоматічный процес',
 'right-nominornewtalk' => 'Невыписованя новых повідомлїнь по малых управах діскузной сторінкы',
@@ -1290,7 +1303,7 @@ $1",
 'right-markbotedits' => 'Означованя ревертів як едітованя робота',
 'right-noratelimit' => 'Не має обмеджіня в швыдкости',
 'right-import' => 'Імпорт сторінок з іншых вікі',
-'right-importupload' => 'Ð\86мпоÑ\80Ñ\82 Ñ\81Ñ\82оÑ\80Ñ\96нок Ñ\87еÑ\80ез Ð½Ð°Ð³Ñ\80аваня файлів',
+'right-importupload' => 'Ð\86мпоÑ\80Ñ\82 Ñ\81Ñ\82оÑ\80Ñ\96нок Ñ\87еÑ\80ез Ð·Ð°Ð»Ð°Ð´Ð¾ваня файлів',
 'right-patrol' => 'Позначованя едітовань як перевіреных',
 'right-autopatrol' => 'Автоматічне означованя едітовань як перевіреных',
 'right-patrolmarks' => 'Зобразованя патролёваных сторінок в Послїднїх змінах',
@@ -1321,10 +1334,10 @@ $1",
 'action-move-subpages' => 'переменованя той сторінкы зо вшыткыма єй підсторінками',
 'action-move-rootuserpages' => 'переменовати корінёвы сторінкы хостователїв',
 'action-movefile' => 'переменовати тот файл',
-'action-upload' => 'нагÑ\80ати тот файл',
+'action-upload' => 'заладовати тот файл',
 'action-reupload' => 'переписати тот екзістуючій файл',
 'action-reupload-shared' => 'перекрыти тот файл зо сполочного уложыштя',
-'action-upload_by_url' => 'нагÑ\80ати тот файл з URL адресы',
+'action-upload_by_url' => 'заладовати тот файл з URL адресы',
 'action-writeapi' => 'хосновати API про писаня',
 'action-delete' => 'Вымазати тоту сторінку',
 'action-deleterevision' => 'вымазати тоту ревізію сторінкы',
@@ -1395,29 +1408,29 @@ $1",
 'recentchangeslinked-to' => 'Вказати зміны на сторінках, одказуючіх на задану сторінку',
 
 # Upload
-'upload' => 'Ð\9dагÑ\80ати файл',
-'uploadbtn' => 'Ð\9dаÑ\87Ñ\96Ñ\82ати файл',
-'reuploaddesc' => 'Ð\97Ñ\80Ñ\83Ñ\88Ñ\8bÑ\82и Ð½Ð°Ñ\87Ñ\96Ñ\82анÑ\8f Ð° Ð²ÐµÑ\80нÑ\83Ñ\82и Ñ\81Ñ\8f Ð´Ð¾ Ñ\84оÑ\80мÑ\8b Ð½Ð°Ñ\87Ñ\96Ñ\82аня',
+'upload' => 'Ð\97аладовати файл',
+'uploadbtn' => 'Ð\97аладовати файл',
+'reuploaddesc' => 'Ð\97Ñ\80Ñ\83Ñ\88Ñ\8bÑ\82и Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²Ð°Ð½Ñ\8f Ð° Ð²ÐµÑ\80нÑ\83Ñ\82и Ñ\81Ñ\8f Ð´Ð¾ Ñ\84оÑ\80мÑ\8b Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²аня',
 'upload-tryagain' => 'Уложыти зміненый попис файлу',
 'uploadnologin' => 'Не сьте приголошеный(а)',
-'uploadnologintext' => 'Ð\9fÑ\80о Ð½Ð°Ñ\87Ñ\96Ñ\82анÑ\8f Ñ\84айлÑ\83 Ñ\81Ñ\8f Ð¼Ñ\83Ñ\81иÑ\82е [[Special:UserLogin|приголосити]].',
-'upload_directory_missing' => 'Ð\90дÑ\80еÑ\81аÑ\80Ñ\8c Ð¿Ñ\80о Ð½Ð°Ð³Ñ\80аванÑ\8f Ñ\84айлÑ\96в ($1) Ñ\85Ñ\8bбиÑ\82Ñ\8c Ñ\96 вебовый сервер го не годен створити.',
-'upload_directory_read_only' => 'Ð\94о Ð°Ð´Ñ\80еÑ\81аÑ\80Ñ\8f Ð¿Ñ\80о Ð½Ð°Ñ\87Ñ\96Ñ\82анÑ\8b Ñ\84айлÑ\8b ($1) Ð½Ðµ Ð¼Ð°Ñ\94 вебовый сервер права запису.',
-'uploaderror' => 'Ð\9fÑ\80и Ð½Ð°Ñ\87Ñ\96Ñ\82аванÑ\8e Ð´Ñ\96Ñ\88ло ÐºÑ\83 Ñ\85Ñ\8bби',
+'uploadnologintext' => 'Ð\96ебÑ\8b Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ñ\82и Ñ\84айл, Ð¼Ñ\83Ñ\81иÑ\82е Ñ\81Ñ\8f [[Special:UserLogin|приголосити]].',
+'upload_directory_missing' => 'Ð\90дÑ\80еÑ\81аÑ\80Ñ\8c Ð¿Ñ\80о Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²Ð°Ð½Ñ\8f Ñ\84айлÑ\96в ($1) Ñ\85Ñ\8bбиÑ\82Ñ\8c Ñ\82ай вебовый сервер го не годен створити.',
+'upload_directory_read_only' => 'Ð\94о Ð°Ð´Ñ\80еÑ\81аÑ\80Ñ\8f Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8bÑ\85 Ñ\84айлÑ\96в ($1) Ð½Ðµ Ð¼Ð°Ñ\82Ñ\8c вебовый сервер права запису.',
+'uploaderror' => 'Ð\9fÑ\96д Ñ\87аÑ\81 Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²Ð°Ð½Ñ\8f Ñ\81Ñ\8f Ð¿Ñ\80иÑ\82Ñ\80аÑ\84ила Ñ\85Ñ\8bба',
 'upload-recreate-warning' => "'''Увага: Файл з тов назвов быв скоре змазаный ці переменованый.'''
 
 Ту є про перегляд зображеный список мазаня і переменованя той сторінкы:",
-'uploadtext' => "Ниже даный формулар служыть про начітаваня файлів. Уж начітаны файлы собі можете перезерати і глядати помочов [[Special:FileList|списку начітаных файлів]], качде начітаня ся тыж зазначує до [[Special:Log/upload|книгы начітаваня]], змазаня суть в [[Special:Log/delete|книзї змазаных сторінок]].
+'uploadtext' => "Ниже даный формуларь служыть на заладовованя файлів. Уж заладованы файлы собі можете перезерати і глядати помочов [[Special:FileList|списку заладованых файлів]], кажде заладованя ся тыж зазначує до [[Special:Log/upload|книгы заладованя]], змазаня суть в [[Special:Log/delete|книзї змазаных сторінок]].
 
 Про вложіня образку до сторінкы хоснуйте єден з наслїдуючіх способів запису:
-* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Файл.jpg]]</nowiki></code>''' Ð´Ð¾ Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ð²Ð»Ð¾Ð¶Ñ\8bÑ\82Ñ\8c Ñ\86Ñ\96лый образок,
+* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Файл.jpg]]</nowiki></code>''' Ð´Ð¾ Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ð²Ð»Ð¾Ð¶Ñ\8bÑ\82Ñ\8c Ñ\86Ñ\97лый образок,
 * '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Файл.png|thumb|left|Попис]]</nowiki></code>''' вложыть нагляд в рамику зарівнанім на лівый бік, з пописом „Попис“,
-* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Файл.ogg]]</nowiki></code>''' Ð²Ð»Ð¾Ð¶Ñ\8bÑ\82Ñ\8c Ð¿Ñ\80Ñ\8fмÑ\8bй одказ на файл, без того жебы ся зобразив на сторінцї.",
-'upload-permitted' => 'Доволены тіпы  файлів: $1.',
+* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Файл.ogg]]</nowiki></code>''' Ð²Ð»Ð¾Ð¶Ñ\8bÑ\82Ñ\8c Ð´Ñ\96Ñ\80екÑ\82 одказ на файл, без того жебы ся зобразив на сторінцї.",
+'upload-permitted' => 'Ð\94озволенÑ\8b Ñ\82Ñ\96пÑ\8b  Ñ\84айлÑ\96в: $1.',
 'upload-preferred' => 'Преферованы тіпы файлів: $1',
 'upload-prohibited' => 'Заказаны тіпы файлів: $1.',
-'uploadlog' => 'книга Ð½Ð°Ð³Ñ\80аваня',
-'uploadlogpage' => 'Ð\9bоÒ\91 Ð½Ð°Ð³Ñ\80аных файлів',
+'uploadlog' => 'книга Ð·Ð°Ð»Ð°Ð´Ð¾ваня',
+'uploadlogpage' => 'Ð\9bоÒ\91 Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аных файлів',
 'uploadlogpagetext' => 'Ниже найдете список найновшых файлів. Смотьте [[Special:NewFiles|ґалерію новых образків]] про веце візуалного нагляду.',
 'filename' => 'Назва файлу:',
 'filedesc' => 'Попис',
@@ -1425,30 +1438,30 @@ $1",
 'filereuploadsummary' => 'Зміны у файлі:',
 'filestatus' => 'Авторьскы права:',
 'filesource' => 'Жрідло:',
-'uploadedfiles' => 'Ð\9dаÑ\87Ñ\96Ñ\82аны файлы',
-'ignorewarning' => 'Іґноровати варованя тай начітати файл.',
+'uploadedfiles' => 'Ð\97аладованы файлы',
+'ignorewarning' => 'Іґноровати варованя тай уложыти файл.',
 'ignorewarnings' => 'Іґноровати вшыткы варованя',
 'minlength1' => 'Назва файлу мусить мати холем єдну літеру.',
-'illegalfilename' => 'Ð\9dазва Ñ\84айлÑ\83 "$1" Ð¾Ð±Ñ\81Ñ\8fгÑ\83Ñ\94 Ð±Ñ\83квÑ\8b, ÐºÐ¾Ñ\82Ñ\80Ñ\8b Ð½Ðµ Ñ\81Ñ\83Ñ\82Ñ\8c Ð¿Ð¾Ð²Ð¾Ð»ÐµÐ½Ñ\8b Ð² Ð½Ð°Ð·Ð²Ð°Ñ\85 Ñ\81Ñ\82оÑ\80Ñ\96нок. Ð\9fÑ\80оÑ\81име, Ð¿ÐµÑ\80еменÑ\83йÑ\82е Ñ\84айл Ñ\96 Ñ\81пÑ\80обÑ\83йÑ\82е Ð³Ð¾ Ð½Ð°Ð³Ñ\80ати зясь.',
+'illegalfilename' => 'Ð\9dазва Ñ\84айлÑ\83 "$1" Ð¾Ð±Ñ\81Ñ\8fгÑ\83Ñ\94 Ð±Ñ\83квÑ\8b, ÐºÐ¾Ñ\82Ñ\80Ñ\8b Ð½Ðµ Ñ\81Ñ\83Ñ\82Ñ\8c Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ñ\8b Ð² Ð½Ð°Ð·Ð²Ð°Ñ\85 Ñ\81Ñ\82оÑ\80Ñ\96нок. Ð\9fÑ\80оÑ\81име, Ð¿ÐµÑ\80еменÑ\83йÑ\82е Ñ\84айл Ñ\96 Ñ\81пÑ\80обÑ\83йÑ\82е Ð³Ð¾ Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ати зясь.',
 'filename-toolong' => 'Назвы файлів не можуть быти довшы, як 240 байтів.',
 'badfilename' => 'Назва файлу была змінена на „$1“.',
 'filetype-mime-mismatch' => 'Росшырїня файлу ".$1" не одповідать ёго MIME тіпу ($2).',
-'filetype-badmime' => 'Ð\9dе Ñ\94 Ð¿Ð¾Ð²Ð¾Ð»ÐµÐ½Ðµ Ð½Ð°Ñ\87Ñ\96Ñ\82ати файлы MIME тіп „$1“.',
-'filetype-bad-ie-mime' => 'Ð\9dеможно Ð½Ð°Ñ\87Ñ\96Ñ\82аÑ\82и Ñ\82оÑ\82 Ñ\84айл, Ð±Ð¾ Internet Explorer Ð±Ñ\8b Ð³Ð¾ Ð¿Ð¾Ð²Ð°Ð¶Ð¾Ð²Ð°Ð² Ð·Ð° â\80\9e$1â\80\9c, Ñ\88Ñ\82о Ñ\94 Ð½Ðµ Ð´Ð¾Ð²Ð¾Ð»ÐµÐ½Ñ\8bй Ñ\96 Ð¿Ð¾Ñ\82енÑ\86Ñ\96ално Ð½ÐµÐ±ÐµÐ·печный тіп файлу.',
+'filetype-badmime' => 'Ð\9dе Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¾ Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²ати файлы MIME тіп „$1“.',
+'filetype-bad-ie-mime' => 'Ð\9dеможливо Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ñ\82и Ñ\82оÑ\82 Ñ\84айл, Ð±Ð¾ Internet Explorer Ð±Ñ\8b Ð³Ð¾ Ñ\82Ñ\80имав Ð·Ð° â\80\9e$1â\80\9c, Ñ\88Ñ\82о Ñ\94 Ð½Ðµ Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ñ\8bй Ñ\96 Ð¿Ð¾Ñ\82енÑ\86Ñ\96ално Ð½ÐµÐ±ÐµÑ\81печный тіп файлу.',
 'filetype-unwanted-type' => "„.$1“''' є нежеланый формат файлу. {{plural:$3|Желаный формат файлів є|Желаны форматы файлів суть}} $2.",
-'filetype-banned-type' => "'''â\80\9e.$1â\80\9c''' {{PLURAL:$4|Ñ\94 Ð½ÐµÐ´Ð¾Ð²Ð¾Ð»ÐµÐ½Ñ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84айлÑ\96в|Ñ\81Ñ\83Ñ\82Ñ\8c Ð½ÐµÐ´Ð¾волены форматы файлів}}.
-{{PLURAL:$3|Ð\94оволенÑ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84алÑ\96в|Ð\94оволены форматы файлів суть}} $2.",
-'filetype-missing' => 'Файл Ð½Ðµ Ð¼Ð°Ñ\94 Ñ\80оÑ\81Ñ\88Ñ\8bÑ\80Ñ\96ня (наприклад, «.jpg»).',
-'empty-file' => 'Ð\9dаÑ\87Ñ\96Ñ\82аный файл є порожнїй.',
-'file-too-large' => 'Ð\9dаÑ\87Ñ\96Ñ\82аный файл є барз великый.',
+'filetype-banned-type' => "'''â\80\9e.$1â\80\9c''' {{PLURAL:$4|Ñ\94 Ð½ÐµÐ´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ñ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84айлÑ\96в|Ñ\81Ñ\83Ñ\82Ñ\8c Ð½ÐµÐ´Ð¾Ð·волены форматы файлів}}.
+{{PLURAL:$3|Ð\94озволенÑ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84алÑ\96в|Ð\94озволены форматы файлів суть}} $2.",
+'filetype-missing' => 'Файл Ð½Ðµ Ð¼Ð°Ñ\94 Ñ\80оÑ\81Ñ\88Ñ\8bÑ\80Ñ\97ня (наприклад, «.jpg»).',
+'empty-file' => 'Ð\97аладованый файл є порожнїй.',
+'file-too-large' => 'Ð\97аладованый файл є барз великый.',
 'filename-tooshort' => 'Назва файлу є барз коротка.',
 'filetype-banned' => 'Тот тіп файлу є заказаный.',
 'verification-error' => 'Тот файл не перешов овіринём файлів.',
 'hookaborted' => 'Пожадована вами зміна была одмітнута дакотрым росшырінём.',
-'illegal-filename' => 'ТоÑ\82а Ð½Ð°Ð·Ð²Ð° Ñ\84айлÑ\83 Ð½Ðµ Ñ\94 Ð¿Ð¾волена.',
-'overwrite' => 'Ð\9dе Ñ\94 Ð´Ð¾Ð²Ð¾Ð»ÐµÐ½Ðµ переписати екзістуючій файл.',
-'unknown-error' => 'Ð\94Ñ\96Ñ\88ло ÐºÑ\83 Ð½ÐµÐ·Ð½Ð°Ð¼Ñ\96й Ñ\85Ñ\8bбÑ\96.',
-'tmp-create-error' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся створити дочасный файл.',
+'illegal-filename' => 'ТоÑ\82а Ð½Ð°Ð·Ð²Ð° Ñ\84айлÑ\83 Ð½Ðµ Ñ\94 Ð´Ð¾Ð·волена.',
+'overwrite' => 'Ð\9dе Ñ\81лободно переписати екзістуючій файл.',
+'unknown-error' => 'ТÑ\80аÑ\84ила Ñ\81Ñ\8f Ð½ÐµÐ·Ð½Ð°Ð¼Ð° Ñ\85Ñ\8bба.',
+'tmp-create-error' => 'Ð\9dе Ð²Ð´Ð°ло ся створити дочасный файл.',
 'tmp-write-error' => 'Хыба запису до дочасного файлу.',
 'large-file' => 'Ся рекомендує, жебы довжка файлу непересяговала $1, тот файл має $2.',
 'largefileserver' => 'Розмір файлу є векшый як ліміт наставленый на сервері.',
@@ -1459,66 +1472,66 @@ $1",
 'filepageexists' => "Пописова сторінка про файл з тов назвов уж была на  '''<tt>[[:$1]]</tt>''' створена, але одповідаючій файл дотеперь не екзістує.
 Згорнутя, котре ту зазначіте, ся на пописовій сторінцї не зобразить.
 Кідь там хочете своє згорнутя зобразити, будете мусити дану сторінку едітовати мануално. [[$1|thumb]]",
-'fileexists-extension' => "Уже екзістує файл з подобным меном: [[$2|thumb]]
-* Ð\9dазва Ð½Ð°Ñ\87Ñ\96Ñ\82аного файлу: '''<tt>[[:$1]]</tt>'''
+'fileexists-extension' => "Уже екзістує файл з подобным іменом: [[$2|thumb]]
+* Ð\9dазва Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аного файлу: '''<tt>[[:$1]]</tt>'''
 * Назва екзістуючого файлу: '''<tt>[[:$2]]</tt>'''
 Выберте іншу назву.",
-'fileexists-thumbnail-yes' => "Тот файл є асі образок в зменшеній великости ''(нагляд)''. [[$1|thumb]]
+'fileexists-thumbnail-yes' => "Тот файл є асі образчік в зменшеній великости ''(нагляд)''. [[$1|thumb]]
 Перевірте файл '''<tt>[[:$1]]</tt>'''.
\9fокÑ\8b Ñ\94 Ð²ÐºÐ°Ð·Ð°Ð½Ñ\8bй Ñ\84айл Ð²ÐµÐºÑ\88Ñ\8bй, Ð°Ð»Ðµ Ñ\96накÑ\88е Ñ\80овнакÑ\8bй, Ð½Ðµ Ñ\94 Ñ\82Ñ\80еба Ð¾ÐºÑ\80емо Ð½Ð°Ñ\87Ñ\96Ñ\82ати ёго зменшену верзію.",
\9aÑ\96дÑ\8c Ñ\94 Ð²ÐºÐ°Ð·Ð°Ð½Ñ\8bй Ñ\84айл Ð²ÐµÐºÑ\88Ñ\8bй, Ð°Ð»Ðµ Ñ\96накÑ\88е Ñ\94днакÑ\8bй, Ð½Ðµ Ñ\82Ñ\80еба Ð¾ÐºÑ\80емо Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ати ёго зменшену верзію.",
 'file-thumbnail-no' => "Назва файлу ся зачінать на '''<tt>$1</tt>'''.
-Може є то образок в зменшеній великости ''(нагляд)''.
\9dаÑ\87Ñ\96Ñ\82айте файл в повнім розлишіню, покы є ку діспозіції, або зміньте назву файлу.",
-'fileexists-forbidden' => 'Файл Ð· Ñ\82аков Ð½Ð°Ð·Ð²Ð¾Ð² Ñ\83ж ÐµÐºÐ·Ñ\96Ñ\81Ñ\82Ñ\83Ñ\94 Ñ\96 Ð½Ðµ Ñ\94 Ð¿Ð¾Ð²Ð¾Ð»ÐµÐ½Ðµ го переписати.
\9aÑ\96дÑ\8c Ñ\85оÑ\87еÑ\82е Ñ\82оÑ\82 Ñ\84айл Ð½Ð°Ñ\87Ñ\96Ñ\82ати, вернийте ся і звольте іншу назву.
+Може є то образчік в зменшеній великости ''(нагляд)''.
\97аладÑ\83йте файл в повнім розлишіню, покы є ку діспозіції, або зміньте назву файлу.",
+'fileexists-forbidden' => 'Файл Ð· Ñ\82аков Ð½Ð°Ð·Ð²Ð¾Ð² Ñ\83ж ÐµÐºÐ·Ñ\96Ñ\81Ñ\82Ñ\83Ñ\94 Ñ\96 Ð½Ðµ Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¾ го переписати.
\9aÑ\96дÑ\8c Ñ\85оÑ\87еÑ\82е Ñ\82оÑ\82 Ñ\84айл Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ати, вернийте ся і звольте іншу назву.
 [[File:$1|thumb|center|$1]]',
-'fileexists-shared-forbidden' => 'Файл з тов назвов уж екзістує в здїлянім усховіщу. Кідь і наперек тому хочете ваш файл начітати, вернийте ся і звольте іншу назву. [[File:$1|thumb|center|$1]]',
+'fileexists-shared-forbidden' => 'Файл з тов назвов уж екзістує в сполочнім усховищу. Кідь і наперек тому хочете ваш файл заладовати, вернийте ся і звольте іншу назву. [[File:$1|thumb|center|$1]]',
 'file-exists-duplicate' => 'Тот файл є дуплікат {{PLURAL:$1|файлу|такых файлів}}:',
-'file-deleted-duplicate' => 'Ð\86денÑ\82Ñ\96Ñ\87нÑ\8bй Ñ\84айл ÐºÑ\83 Ñ\82омÑ\83 ([[:$1]]) Ð±Ñ\8bв Ñ\83ж Ñ\81коÑ\80е Ð·Ð¼Ð°Ð·Ð°Ð½Ñ\8bй. Ð\9fеÑ\80едÑ\82Ñ\8bм Ñ\8fк Ñ\84айл Ð·Ð½Ð¾Ð²Ñ\83 Ð½Ð°Ð³Ñ\80аєте, бы сьте мали перевірити записы о попереднёму змазаню.',
-'uploadwarning' => 'Увага Ð¿Ñ\80о Ð½Ð°Ñ\87Ñ\96Ñ\82анÑ\8f',
+'file-deleted-duplicate' => 'Ð\86денÑ\82Ñ\96Ñ\87нÑ\8bй Ñ\84айл ÐºÑ\83 Ñ\82омÑ\83 ([[:$1]]) Ð±Ñ\8bв Ñ\83ж Ñ\81коÑ\80е Ð·Ð¼Ð°Ð·Ð°Ð½Ñ\8bй. Ð\9fеÑ\80едÑ\82Ñ\8bм Ñ\8fк Ñ\84айл Ð·Ð½Ð¾Ð²Ñ\83 Ð·Ð°Ð»Ð°Ð´Ñ\83єте, бы сьте мали перевірити записы о попереднёму змазаню.',
+'uploadwarning' => 'Ð\9fозÑ\96Ñ\80Ñ\8c Ðº Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8e',
 'uploadwarning-text' => 'Просиме, зміньте опис файлу ниже і спробуйте то знову.',
 'savefile' => 'Уложыти файл',
-'uploadedimage' => 'нагÑ\80ав "[[$1]]"',
-'overwroteimage' => 'наÑ\87Ñ\96Ñ\82ана нова верзія "[[$1]]"',
-'uploaddisabled' => 'Ð\9dаÑ\87Ñ\96Ñ\82анÑ\8f Ñ\84айлÑ\96в Ð·Ð°Ð±Ð¾Ñ\80онене',
-'copyuploaddisabled' => 'Ð\9dаÑ\87Ñ\96Ñ\82аня файлів через URL є выпнуте.',
-'uploadfromurl-queued' => 'Ð\92аÑ\88а Ð¿Ð¾Ð¶Ð°Ð´Ð°Ð²ÐºÐ° Ð¿Ñ\80о Ð½Ð°Ñ\87Ñ\96Ñ\82аня файлу была уложена до фронты.',
-'uploaddisabledtext' => 'Ð\9dаÑ\87Ñ\96Ñ\82аня файлів є выпнуте.',
-'php-uploaddisabledtext' => 'Ð\92 PHP Ñ\94 Ð²Ñ\8bпнÑ\83Ñ\82е Ð½Ð°Ñ\87Ñ\96Ñ\82анÑ\8f Ñ\84айлÑ\96в. Ð\9fÑ\80оÑ\81име, Ð¿ÐµÑ\80евÑ\96Ñ\80те наставлїня file_uploads.',
+'uploadedimage' => 'заладовав "[[$1]]"',
+'overwroteimage' => 'заладована нова верзія "[[$1]]"',
+'uploaddisabled' => 'Ð\97аладовованÑ\8f Ñ\84айлÑ\96в Ð·Ð°Ð±Ð¾Ñ\80онене.',
+'copyuploaddisabled' => 'Ð\97аладовованя файлів через URL є выпнуте.',
+'uploadfromurl-queued' => 'Ð\92аÑ\88а Ð¿Ð¾Ð¶Ð°Ð´Ð°Ð²ÐºÐ° Ð½Ð° Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²аня файлу была уложена до фронты.',
+'uploaddisabledtext' => 'Ð\97аладовованя файлів є выпнуте.',
+'php-uploaddisabledtext' => 'Ð\92 PHP Ñ\94 Ð²Ñ\8bпнÑ\83Ñ\82е Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²Ð°Ð½Ñ\8f Ñ\84айлÑ\96в. Ð\9fÑ\80оÑ\81име, Ð¿ÐµÑ\80евÑ\96Ñ\80Ñ\8cте наставлїня file_uploads.',
 'uploadscripted' => 'Тот файл обсягує HTML-код або скріпт, якый може быти неправилно інтерпретованый вебовым переглядячом.',
 'uploadvirus' => 'Файл обсягує вірус! Детайлы: $1',
 'uploadjava' => 'Тот файл є ZIP архів, котрый обсягує .class-файл Java.
\9dаÑ\87Ñ\96Ñ\82анÑ\8f Java-Ñ\84айлÑ\96в Ð½Ðµ Ñ\94 Ð´Ð¾Ð²Ð¾Ð»ÐµÐ½Ðµ, Ð±Ð¾ Ð¾Ð½Ð¸ Ð¼Ð¾Ð¶Ñ\83Ñ\82Ñ\8c Ð·Ð°Ð¿Ñ\80иÑ\87Ñ\96ниÑ\82и Ð¾Ð±Ñ\85од Ð·Ð°Ð±ÐµÐ·печіня сістемы.',
\97аладованÑ\8f Java-Ñ\84айлÑ\96в Ð½Ðµ Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ðµ, Ð±Ð¾ Ð¾Ð½Ð¸ Ð¼Ð¾Ð¶Ñ\83Ñ\82Ñ\8c Ð·Ð°Ð¿Ñ\80Ñ\96Ñ\87Ñ\96ниÑ\82и Ð¾Ð±Ñ\85од Ð·Ð°Ð±ÐµÑ\81печіня сістемы.',
 'upload-source' => 'Жрідловый файл',
 'sourcefilename' => 'Назва жрідлового файлу:',
 'sourceurl' => 'Жрідлова URL-адреса:',
 'destfilename' => 'Назва цілёвого файлу:',
 'upload-maxfilesize' => 'Максімалный розмір файлу: $1',
 'upload-description' => 'Попис файлу',
-'upload-options' => 'Ð\9fаÑ\80амеÑ\82Ñ\80Ñ\8b Ð½Ð°Ñ\87Ñ\96Ñ\82аня',
+'upload-options' => 'Ð\9fаÑ\80амеÑ\82Ñ\80Ñ\8b Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾Ð²аня',
 'watchthisupload' => 'Слїдовати тот файл',
 'filewasdeleted' => 'Файл з таков назвов уж екзістовав а быв змазаный. Детайлы обсягує $1.',
-'filename-bad-prefix' => "Назва файлу, котрый начітавате ся зачінать на '''„$1“''', што не є назва звычайно приряджована діґіталным фотоапаратом. Звольте іншу назву, котра ваш файл попише лїпше.",
-'upload-success-subj' => 'Ð\9dаÑ\87Ñ\96Ñ\82аня было успішне',
-'upload-success-msg' => 'Файл Ð²Ð°Ð¼Ð¸ Ð½Ð°Ñ\87Ñ\96Ñ\82аный з [$2] є доступный на [[:{{ns:file}}:$1]]',
-'upload-failure-subj' => 'Проблем з начітаным файлом',
-'upload-failure-msg' => 'У Ð²Ð°Ð¼Ð¸ Ð½Ð°Ñ\87Ñ\96Ñ\82аного Ñ\84айлÑ\83 Ð²Ð·Ð½Ð¸ÐºÐ½Ñ\83в проблем  з [$2]::
+'filename-bad-prefix' => "Назва заладовованого файлу ся зачінать на '''„$1“''', што не є назва звычайно приряджована діґіталным фотоапаратом. Звольте іншу назву, котра ваш файл попише лїпше.",
+'upload-success-subj' => 'Ð\97аладованя было успішне',
+'upload-success-msg' => 'Файл Ð²Ð°Ð¼Ð¸ Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аный з [$2] є доступный на [[:{{ns:file}}:$1]]',
+'upload-failure-subj' => 'Проблем із заладованём',
+'upload-failure-msg' => 'У Ð²Ð°Ð¼Ð¸ Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ð¾Ð³Ð¾ Ñ\84айлÑ\83 Ð²Ñ\8bник проблем  з [$2]::
 
 $1',
-'upload-warning-subj' => 'Увага Ð¿Ñ\80о Ð½Ð°Ñ\87Ñ\96Ñ\82анÑ\8f',
-'upload-warning-msg' => 'Почас вашого начітаваня файлу [$2] ся став проблем. Кідь го хочете вырїшыти, можете ся вернути до  [[Special:Upload/stash/$1|формуларя начітаваня]].',
+'upload-warning-subj' => 'Ð\9fозÑ\96Ñ\80Ñ\8c Ðº Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8e',
+'upload-warning-msg' => 'Під час вашого заладовованя файлу [$2] ся притрафив проблем. Кідь го хочете вырїшыти, можете ся вернути до  [[Special:Upload/stash/$1|формуларя заладовованя]].',
 
 'upload-proto-error' => 'Неплатный протокол',
-'upload-proto-error-text' => 'Ð\9dагÑ\80анÑ\8f Ð²Ð·Ð´Ð°Ð»ÐµÐ½ого файлу пожадує зазначіня URLs з початком <code>http://</code> або <code>ftp://</code>.',
+'upload-proto-error-text' => 'Ð\97аладованÑ\8f Ð¾Ð´Ð»ÐµÐ³Ð»ого файлу пожадує зазначіня URLs з початком <code>http://</code> або <code>ftp://</code>.',
 'upload-file-error' => 'Інтерна хыба',
-'upload-file-error-text' => 'Почас спробы створїня дочасного файлу настала внутрїшня хыба на сервері.
+'upload-file-error-text' => 'При спробі створити дочасный файл настала внутрїшня хыба на серверї.
 Просиме контактуйте  [[Special:ListUsers/sysop|адміністратора]].',
 'upload-misc-error' => 'Незнама хыба',
-'upload-misc-error-text' => 'Ð\9dезнама Ñ\85Ñ\8bба Ð½Ð°Ñ\81Ñ\82ала Ð¿Ð¾Ñ\87аÑ\81 Ð½Ð°Ð³Ñ\80аванÑ\8f Ñ\84айлÑ\83. Ð\9fеÑ\80евÑ\96Ñ\80Ñ\82е Ñ\86Ñ\96 Ñ\94  URL Ð¿Ð»Ð°Ñ\82на і приступна і спробуйте то знову. Кідь ся хыба обявить знову, контактуйте [[Special:ListUsers/sysop|адміністратора]]. сістемы.',
+'upload-misc-error-text' => 'Ð\9dезнана Ñ\85Ñ\8bба Ñ\81Ñ\8f Ñ\82Ñ\80аÑ\84ила Ð¿Ñ\96д Ñ\87аÑ\81 Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8f Ñ\84айлÑ\83. Ð\9fеÑ\80евÑ\96Ñ\80Ñ\82е Ñ\86Ñ\96 Ñ\94  URL Ð¿Ñ\80авилна і приступна і спробуйте то знову. Кідь ся хыба обявить знову, контактуйте [[Special:ListUsers/sysop|адміністратора]]. сістемы.',
 'upload-too-many-redirects' => 'URL обсягує барз велё напрямлінь',
 'upload-unknown-size' => 'Незнамый розмір',
-'upload-http-error' => 'Ð\94Ñ\96Ñ\88ло ÐºÑ\83 Ñ\85Ñ\8bбÑ\96 HTTP: $1',
-'upload-copy-upload-invalid-domain' => 'Ð\9dаÑ\87Ñ\96Ñ\82аня копірованём неможливе з той домены.',
+'upload-http-error' => 'СÑ\82ала Ñ\81Ñ\8f Ñ\85Ñ\8bба HTTP: $1',
+'upload-copy-upload-invalid-domain' => 'Ð\97аладовованя копірованём неможливе з той домены.',
 
 # File backend
 'backend-fail-stream' => 'Не вдало ся транслёвати файл $1.',
@@ -1541,59 +1554,77 @@ $1',
 'backend-fail-readonly' => 'Кінцёва уложна сістема „$1“ моментално лем на чітаня. Прічіна: „$2“',
 'backend-fail-synced' => 'Файл "$1" в кінцёвій уложній сістемі в неконзістентнім стані',
 'backend-fail-connect' => 'Не вдало ся припоїти до кінцёвой уложной сістемы „$1“.',
-'backend-fail-internal' => 'В кінцёвій уложній сістемі „$1“ дішло к незнаній хыбі.',
+'backend-fail-internal' => 'В кінцёвій уложній сістемі „$1“ ся стала незнама хыба.',
 'backend-fail-contenttype' => 'Не годно было становити тіп обсягу файлу, жебы уложыти го до „$1“.',
-'backend-fail-usable' => 'Не вдало ся записати до файлу $1  про недостаточны права або хыбуючі адресарї/контайнеры.',
+'backend-fail-batchsize' => 'Кінцёве усховище прияло блок з $1 {{PLURAL:файловов операціов|файловыма операціями}};максімум є {{PLURAL:$2|$2}}.',
+'backend-fail-usable' => 'Не вдало ся записати до файлу $1  про брак прав або хыбуючі адресарї/контайнеры.',
+
+# File journal errors
+'filejournal-fail-dbconnect' => 'Не годен ся припоїти к журналовій датабазї усховища «$1».',
+'filejournal-fail-dbquery' => 'Не вдало ся актуалізовати журналову датабазу усховища «$1».',
+
+# Lock manager
+'lockmanager-notlocked' => 'Файл „$1“ не годен одокмнути, бо не є замкнутый.',
+'lockmanager-fail-closelock' => 'Файл із замком про „$1“ не годен заперти.',
+'lockmanager-fail-deletelock' => 'Файл із замком про „$1“ не годен змазати.',
+'lockmanager-fail-acquirelock' => 'Не годен здобыти замок про „$1“.',
+'lockmanager-fail-openlock' => 'Файл із замком про „$1“ не годен отворити.',
+'lockmanager-fail-releaselock' => 'Не годен увольнити замок про „$1“.',
+'lockmanager-fail-db-bucket' => 'Не годен навязати споїня з достаточнов кількостёв датабаз замків в сеґментї $1.',
+'lockmanager-fail-db-release' => 'Замкнутя датабазы $1 не вдало ся увольнити.',
+'lockmanager-fail-svr-acquire' => 'Не вдало ся здобыти замок сервера $1.',
+'lockmanager-fail-svr-release' => 'Замкнутя сервера $1 не вдало ся увольнити.',
 
 # ZipDirectoryReader
 'zip-file-open-error' => 'При одкрытю ZIP-архіву про ёго перевірку выникла хыба.',
 'zip-wrong-format' => 'Вказаный файл не є ZIP-файлом',
 'zip-bad' => 'ZIP-файл є пошкодженый ці в іншый способ непридатный про чітаня.
-Не годен перевірити ёго безпечность.',
+Не годен перевірити ёго беспеку.',
 'zip-unsupported' => 'Файл хоснує такы можности ZIP, якы MediaWiki не підпорує.
-Не годен перевірити ёго безпечность.',
+Не годен перевірити ёго беспеку.',
 
 # Special:UploadStash
-'uploadstash' => 'Ð\9dагÑ\80аÑ\82и Ñ\81кÑ\80Ñ\8bÑ\88Ñ\83',
-'uploadstash-summary' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ð´Ð¾Ð´Ð°Ð²Ð°Ñ\82Ñ\8c Ð¿Ñ\80иÑ\81Ñ\82Ñ\83п ÐºÑ\83 Ñ\84айлом ÐºÐ¾Ñ\82Ñ\80Ñ\8b Ñ\81Ñ\83Ñ\82Ñ\8c Ð½Ð°Ð³Ñ\80аÑ\82Ñ\8b (або Ð½Ð°Ð³Ñ\80аванÑ\8f Ñ\96Ñ\89Ñ\96 Ð½Ðµ Ñ\81кÑ\96нÑ\87Ñ\96ло) Ð°Ð»Ðµ Ñ\96Ñ\89Ñ\96 Ð½Ðµ Ð±Ñ\8bли Ð¿Ñ\83влÑ\96кованÑ\8b Ð½Ð° Ð²Ñ\96кÑ\96. Ð¢Ð¾Ñ\82Ñ\8b Ñ\84айлÑ\8b Ð½Ðµ Ð²Ð¸Ð´Ð¸Ñ\82Ñ\8c Ð½Ð¸Ñ\85Ñ\82о Ð¾ÐºÑ\80ем Ñ\85оÑ\81новаÑ\82елÑ\8f Ñ\88Ñ\82о Ñ\97Ñ\85 Ð½Ð°Ð³Ñ\80ав.',
+'uploadstash' => 'СкÑ\80Ñ\8bÑ\88а Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8bÑ\85 Ñ\84айлÑ\96в',
+'uploadstash-summary' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ð´Ð¾Ð´Ð°Ð²Ð°Ñ\82Ñ\8c Ð¿Ñ\80иÑ\81Ñ\82Ñ\83п ÐºÑ\83 Ñ\84айлом ÐºÐ¾Ñ\82Ñ\80Ñ\8b Ñ\81Ñ\83Ñ\82Ñ\8c Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8b (або Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8f Ñ\96Ñ\89Ñ\96 Ð½Ðµ Ñ\81кÑ\96нÑ\87Ñ\96ло) Ð°Ð»Ðµ Ñ\96Ñ\89Ñ\96 Ð½Ðµ Ð±Ñ\8bли Ð¾Ð¿Ñ\83влÑ\96кованÑ\8b Ð½Ð° Ð²Ñ\96кÑ\96. Ð¢Ð¾Ñ\82Ñ\8b Ñ\84айлÑ\8b Ð½Ðµ Ð²Ð¸Ð´Ð¸Ñ\82Ñ\8c Ð½Ð¸Ñ\85Ñ\82о Ð¾ÐºÑ\80ем Ñ\85оÑ\81новаÑ\82елÑ\8f Ñ\88Ñ\82о Ñ\97Ñ\85 Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ав.',
 'uploadstash-clear' => 'Змазати схованы файлы',
 'uploadstash-nofiles' => 'Не маєте жадны схованы файлы.',
 'uploadstash-badtoken' => 'Выконаня той дїї не было успішне, може зато, же вашы повірїня про едітованя скінчіли. Попробуйте знову.',
 'uploadstash-errclear' => 'Змазаня файлів не было успішне.',
 'uploadstash-refresh' => 'Обновити список файлів',
+'invalid-chunk-offset' => 'Неприступный посув фраґмента.',
 
 # img_auth script messages
 'img-auth-accessdenied' => 'Приступ одопертый',
 'img-auth-nopathinfo' => 'Ваш сервер не є наштелёваный так, жебы давав тоту інформацію.
 Може фунґує помочов CGI і img_auth на нім не може фунґовати.
 Посмотьте https://www.mediawiki.org/wiki/Manual:Image_Authorization.',
-'img-auth-notindir' => 'Пожадована стежка не є в конфіґурованім адресарю з начітаныма файлами.',
+'img-auth-notindir' => 'Пожадована стежка не є в конфіґурованім адресарю із заладованыма файлами.',
 'img-auth-badtitle' => 'З „$1“  ся не дасть створити платна назва сторінкы.',
 'img-auth-nologinnWL' => 'Не сьте приголошеный і „$1“ не є на білім списку.',
 'img-auth-nofile' => 'Файл «$1» не екзістує.',
 'img-auth-isdir' => 'Пробуєте приступовати до адресаря „$1“.
-Доволеный є лем приступ к файлам.',
\94озволенÑ\8bй Ñ\94 Ð»ÐµÐ¼ Ð¿Ñ\80иÑ\81Ñ\82Ñ\83п Ðº Ñ\84айлам.',
 'img-auth-streaming' => 'Переношать ся „$1“.',
-'img-auth-public' => 'Помочов img_auth.php ся поскытують файлы на пріватных вікі.
+'img-auth-public' => 'Помочов img_auth.php ся придавають файлы з пріватных вікі.
 Тота вікі є наставлена як публічна.
-З безпечностных причін є img_auth.php выпнуте.',
+З беспечностных прічін є img_auth.php выпнуте.',
 'img-auth-noread' => 'Хоснователь не має приступ про чітаня „$1“.',
 'img-auth-bad-query-string' => 'URL обсягує неправилный одказ.',
 
 # HTTP errors
 'http-invalid-url' => 'Неправилне URL: $1',
 'http-invalid-scheme' => 'URL хоснуючі схемы „$1“  не суть підпорованы.',
-'http-request-error' => 'Ð\9dезнама Ñ\85Ñ\8bба Ð¿Ð¾Ñ\87аÑ\81 Ð¾Ð´Ð¾Ñ\81Ñ\8bланÑ\8f пожадавкы.',
-'http-read-error' => 'Хыба почас чітаня HTTP.',
+'http-request-error' => 'Ð\9dезнана Ñ\85Ñ\8bба Ð¿Ñ\80и Ð¾Ð´Ð¾Ñ\81Ñ\8bланÑ\8e пожадавкы.',
+'http-read-error' => 'Хыба чітаня HTTP.',
 'http-timed-out' => 'Час про HTTP пожадавкы уплинув.',
 'http-curl-error' => 'Хыба при чітаню з URL: $1',
-'http-host-unreachable' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило Ñ\81Ñ\8f ÐºÐ¾Ð½Ñ\82акÑ\80овати URL.',
-'http-bad-status' => 'Почас HTTP пожадавкы настав проблем: $1 $2',
+'http-host-unreachable' => 'Ð\9dе Ð²Ð´Ð°Ð»Ð¾ Ñ\81Ñ\8f Ð´Ð¾Ñ\81Ñ\8fгнÑ\83ти URL.',
+'http-bad-status' => 'Під час HTTP пожадавкы притрафив ся проблем: $1 $2',
 
 # Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
-'upload-curl-error6' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило ся досягнути URL.',
+'upload-curl-error6' => 'Ð\9dе Ð²Ð´Ð°ло ся досягнути URL.',
 'upload-curl-error6-text' => 'Із зазначеной URL ся не дасть чітати. Перевірте ці є URL правилно написана і сервер є доступный.',
-'upload-curl-error28' => 'ЧаÑ\81 Ð¿Ñ\80о Ð½Ð°Ð³Ñ\80аванÑ\8f Ñ\83плинÑ\83в',
+'upload-curl-error28' => 'ЧаÑ\81 Ð²Ñ\8bдÑ\97ленÑ\8bй Ð½Ð° Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8f Ñ\83ж Ð²Ñ\8bÑ\87еÑ\80Ñ\8cпанÑ\8bй',
 'upload-curl-error28-text' => 'Сервер довго не одповідать. Перевірте ці є доступный і кус почекайте і спробуйте то знову.',
 
 'license' => 'Ліценцованя:',
@@ -1604,8 +1635,8 @@ $1',
 'upload_source_file' => ' (файл на вашім компютерї)',
 
 # Special:ListFiles
-'listfiles-summary' => 'ТоÑ\82а Ñ\88пеÑ\86Ñ\96ална Ñ\81Ñ\82оÑ\80Ñ\96нка Ð·Ð¾Ð±Ñ\80ажÑ\83Ñ\94 Ð²Ñ\88Ñ\8bÑ\82кÑ\8b Ð½Ð°Ñ\87Ñ\96Ñ\82аны файлы.
\9fÑ\80и Ñ\84Ñ\96лÑ\82Ñ\80ованÑ\8e Ð¿Ð¾Ð´Ð»Ñ\8f Ñ\85оÑ\81новаÑ\82елÑ\8f Ñ\81Ñ\8f Ð·Ð¾Ð±Ñ\80ажÑ\83Ñ\8eÑ\82Ñ\8c Ð»ÐµÐ¼ Ñ\84айлÑ\8b, Ñ\83 ÐºÐ¾Ñ\82Ñ\80Ñ\8bÑ\85 Ñ\85оÑ\81новаÑ\82елÑ\8c Ð½Ð°Ñ\87Ñ\96Ñ\82ав актуалну верзію.',
+'listfiles-summary' => 'ТоÑ\82а Ñ\88пеÑ\86Ñ\96ална Ñ\81Ñ\82оÑ\80Ñ\96нка Ð·Ð¾Ð±Ñ\80ажÑ\83Ñ\94 Ð²Ñ\88Ñ\8bÑ\82кÑ\8b Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аны файлы.
\9fÑ\80и Ñ\84Ñ\96лÑ\82Ñ\80ованÑ\8e Ð·Ð° Ñ\85оÑ\81новаÑ\82елÑ\91м, Ð·Ð¾Ð±Ñ\80ажÑ\83Ñ\8eÑ\82Ñ\8c Ñ\81Ñ\8f Ð»ÐµÐ¼ Ñ\84айлÑ\8b, Ñ\83 ÐºÐ¾Ñ\82Ñ\80Ñ\8bÑ\85 Ñ\85оÑ\81новаÑ\82елÑ\8c Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ав актуалну верзію.',
 'listfiles_search_for' => 'Глядати файл по назві:',
 'imgfile' => 'файл',
 'listfiles' => 'Список файлів',
@@ -1648,9 +1679,13 @@ $1',
 Веце інформацій обсягує ёго [$2 сторінка з пописом файлу].',
 'sharedupload-desc-here' => 'Тот файл походить з {{grammar:2sg|$1}} і можуть го хосновати другы проєкты.
 Ниже суть зображены інформації, котры обсягує ёго [$2 сторінка з пописом файлу].',
+'sharedupload-desc-edit' => 'Гевсесь файл походить з {{grammar:2sg|$1}} тай можуть го хосновати другы проєкты.
+Може хочете управити [$2 тамтушню строрінку з пописом файлу].',
+'sharedupload-desc-create' => 'Гевсесь файл походить з {{grammar:2sg|$1}} тай можуть го хосновати другы проєкты.
+Може бы сьте хотїли правити [$2 тамтушню сторінку з пописом файлу].',
 'filepage-nofile' => 'Не екзістує файл з таков назвов',
-'filepage-nofile-link' => 'Файл Ð· Ñ\82аков Ð½Ð°Ð·Ð²Ð¾Ð² Ð½Ðµ ÐµÐºÐ·Ñ\96Ñ\81Ñ\82Ñ\83Ñ\94, Ð°Ð»Ðµ Ð¼Ð¾Ð¶ÐµÑ\82е [$1 Ð³Ð¾ Ð½Ð°Ñ\87Ñ\96Ñ\82ати].',
-'uploadnewversion-linktext' => 'Ð\9dагÑ\80ати нову верзію того файлу',
+'filepage-nofile-link' => 'Файл Ð· Ñ\82аков Ð½Ð°Ð·Ð²Ð¾Ð² Ð½Ðµ ÐµÐºÐ·Ñ\96Ñ\81Ñ\82Ñ\83Ñ\94, Ð°Ð»Ðµ Ð¼Ð¾Ð¶ÐµÑ\82е [$1 Ð³Ð¾ Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ати].',
+'uploadnewversion-linktext' => 'Ð\97аладовати нову верзію того файлу',
 'shared-repo-from' => 'з $1',
 'shared-repo' => 'здїляного усховіща',
 
@@ -1659,7 +1694,7 @@ $1',
 'filerevert-legend' => 'Вернути назад файл',
 'filerevert-intro' => "Вертате назад '''[[Media:$1|$1]]''' на [$4 верзію з $3 $2].",
 'filerevert-comment' => 'Причіна:',
-'filerevert-defaultcomment' => 'Ð\9dавеÑ\80нÑ\83Ñ\82а Ð²ÐµÑ\80зÑ\96Ñ\8f Ð½Ð°Ð³Ñ\80ана Ð²  $2 дня $1.',
+'filerevert-defaultcomment' => 'Ð\9dавеÑ\80нÑ\83Ñ\82а Ð²ÐµÑ\80зÑ\96Ñ\97 Ð· $2 дня $1.',
 'filerevert-submit' => 'Вернути назад',
 'filerevert-success' => "Файл '''[[Media:$1|$1]]''' быв вернутый назад на [$4 верзію з $3 $2].",
 'filerevert-badversion' => 'Не є доступна попередня верзія того файлу з одоповідаючов часовов значков.',
@@ -1686,7 +1721,7 @@ $1',
 
 # MIME search
 'mimesearch' => 'Гляданя по MIME',
-'mimesearch-summary' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ñ\83можнÑ\8eÑ\94 Ñ\84Ñ\96лÑ\82Ñ\80оваÑ\82и Ñ\84айлÑ\8b Ð¿Ð¾Ð´Ð»Ñ\8f Ñ\82Ñ\96пÑ\83 MIME.<br />
+'mimesearch-summary' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ñ\83можнÑ\8eÑ\94 Ñ\84Ñ\96лÑ\82Ñ\80оваÑ\82и Ñ\84айлÑ\8b Ð·Ð° Ñ\82Ñ\96пом MIME.<br />
 Вступ: <code>тіп обсягу/підтіп</code>, наприклад <code>image/jpeg</code>.',
 'mimetype' => 'MIME-тіп:',
 'download' => 'скачати',
@@ -1703,11 +1738,11 @@ $1',
 'unusedtemplateswlh' => 'іншы одказы',
 
 # Random page
-'randompage' => 'Ð\9dагодна статя',
+'randompage' => 'ТÑ\80аÑ\84Ñ\83нкова статя',
 'randompage-nopages' => 'Не є сторінок в {{PLURAL:$2|просторі назв|просторах назв}} $1.',
 
 # Random redirect
-'randomredirect' => 'Ð\9dагодне напрямлїня',
+'randomredirect' => 'ТÑ\80аÑ\84Ñ\83нкове напрямлїня',
 'randomredirect-nopages' => 'Простор назв „$1“ не обсягує жадны напрямлїня.',
 
 # Statistics
@@ -1720,7 +1755,7 @@ $1',
 'statistics-articles' => 'Обсяговы сторінкы',
 'statistics-pages' => 'Сторінкы',
 'statistics-pages-desc' => 'Вшыткы сторінкы на вікі враховано діскузій, напрямлїня ітд.',
-'statistics-files' => 'Ð\9dаÑ\87Ñ\96Ñ\82аны файлы',
+'statistics-files' => 'Ð\97аладованы файлы',
 'statistics-edits' => 'Чісло едітованя од основаня вікі {{SITENAME}}',
 'statistics-edits-average' => 'Середнє чісло едітовань на сторінку',
 'statistics-views-total' => 'Вшыткых переглядів',
@@ -1733,7 +1768,9 @@ $1',
 
 'disambiguations' => 'Сторінкы одказуючі на богатозначны статї',
 'disambiguationspage' => 'Template:disambig',
-'disambiguations-text' => "Одказы на наслїдуючіх сторінках ведуть на '''богатозначны сторінкы'''. (сторінкы котры обсягують дакотру з тых шаблон на [[MediaWiki:Disambiguationspage|списку шаблон про богатозначны сторінкы]]) намісто на дану статю.",
+'disambiguations-text' => "Слїдуючі сторінкы включають найменше єден одказ на '''чеперушку'''.
+Асі намісто того мали бы одказовати на конкретнїшу сторінку.<br />
+Сторінка є тримана за чеперушку, кідь хоснує дакотру із шаблон одказованых на [[MediaWiki:Disambiguationspage]].",
 
 'doubleredirects' => 'Двоїты напрямлїня',
 'doubleredirectstext' => 'На тій сторінцї є список напрямлїн ведучіх на далшы напрямлїня.
@@ -1778,6 +1815,8 @@ $1',
 'wantedpages' => 'Пожадованы статї',
 'wantedpages-badtitle' => 'Резултаты обсягують неправилну назву: $1',
 'wantedfiles' => 'Жаданы файлы',
+'wantedfiletext-cat' => 'Наступны файлы ся хоснують але не існують. Файл з одлеглых усховищ гев можуть быти написасны, бо існують. Такы фалешны позітіва будуть зображены <del>перечаркнути</del>. Сторінкы, котры включають неіснуючі файлы суть іщі к тому написаны на  [[:$1]].',
+'wantedfiletext-nocat' => 'Насупны файлы ся хоснують але не існують. Файлы з одлеглых усховищ гев можуть быти написаны, ай наперек тому же існують. Такы фалешны позітіва будуть зображены <del>перечаркнуты</del>.',
 'wantedtemplates' => 'Хыблячі шаблоны',
 'mostlinked' => 'Найодказованїшы сторінкы',
 'mostlinkedcategories' => 'Найхоснованїшы катеґорії',
@@ -1786,6 +1825,7 @@ $1',
 'mostimages' => 'Найужыванїшы файлы',
 'mostrevisions' => 'Сторінкы з найвеце ревізіями',
 'prefixindex' => 'Вшыткы сторінкы з початком назв',
+'prefixindex-namespace' => 'Вшыткы сторінкы з префіксом (простор назв $1)',
 'shortpages' => 'Курты статї',
 'longpages' => 'Найдовшы статті',
 'deadendpages' => 'Слїпы сторінкы',
@@ -1796,11 +1836,11 @@ $1',
 'protectedpagestext' => 'Наслїдуючі сторінкы суть замкнуты або напів замкнуты про едітованя або переменованя',
 'protectedpagesempty' => 'Жадна сторінка не є замкнута з тыма параметрами.',
 'protectedtitles' => 'Замкнуты назвы сторінок',
-'protectedtitlestext' => 'Наслїдуючі назвы суть замкнуты і не доволены про сторінкы',
+'protectedtitlestext' => 'Ð\9dаÑ\81лÑ\97дÑ\83Ñ\8eÑ\87Ñ\96 Ð½Ð°Ð·Ð²Ñ\8b Ñ\81Ñ\83Ñ\82Ñ\8c Ð·Ð°Ð¼ÐºÐ½Ñ\83Ñ\82Ñ\8b Ñ\96 Ð½Ðµ Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ñ\8b Ð¿Ñ\80о Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b',
 'protectedtitlesempty' => 'Жадна назва не є замкнута з тыма параметрами.',
 'listusers' => 'Список хоснователїв',
 'listusers-editsonly' => 'Вказати лем хоснователїв з едітованями',
-'listusers-creationsort' => 'СоÑ\80Ñ\82оваÑ\82и Ð¿Ð¾Ð´Ð»Ñ\8f Ð´Ð°Ñ\82Ñ\83мÑ\83 створїня',
+'listusers-creationsort' => 'СоÑ\80Ñ\82оваÑ\82и Ð·Ð° Ð´Ð°Ñ\82Ñ\83мом створїня',
 'usereditcount' => '$1 {{PLURAL:$1|едітованя|едітованя|едітовань}}',
 'usercreated' => '{{GENDER:$3|Реґістрованый|Реґістрована|Реґістрованый(а)}} $1 в $2',
 'newpages' => 'Новы сторінкы',
@@ -1836,6 +1876,7 @@ $1',
 Зображіня можете зужыти выбером тіпу запису, мена хоснователя (залежыть на великости букв) або зазначеной сторінкы (тыж залежыть на великости букв).',
 'logempty' => 'Протокол не обсягує жаден одповідаючій запис.',
 'log-title-wildcard' => 'Глядати назвы зачінаючі ся з тым текстом',
+'showhideselectedlogentries' => 'Вказати/сховати зволены записы лоґу.',
 
 # Special:AllPages
 'allpages' => 'Вшыткы сторінкы',
@@ -1851,8 +1892,14 @@ $1',
 'allpagesnext' => 'Далшы',
 'allpagessubmit' => 'Выконати',
 'allpagesprefix' => 'Вказати сторінкы што ся зачінають на:',
-'allpagesbadtitle' => 'Задана назва сторінкы не была платна або обсяговала префікс міджіязыкового або міджівікі одказу. Може обсяговав буквы, котры не суть доволены.',
+'allpagesbadtitle' => 'Задана назва сторінкы не была правилна або обсяговала префікс міджіязыкового або міджівікі одказу. Може обсяговав буквы, котры не суть дозволены.',
 'allpages-bad-ns' => '{{SITENAME}} не має простору назв «$1».',
+'allpages-hide-redirects' => 'Сховати напрямлїня',
+
+# SpecialCachedPage
+'cachedspecial-viewing-cached-ttl' => 'Позерати собі кешовану верзію той сторінкы, котра може быти стара аж $1.',
+'cachedspecial-viewing-cached-ts' => 'Позерати собі кешовану верзію той сторінкы, котра могла стратити актуалность.',
+'cachedspecial-refresh-now' => 'Вказати найновшы.',
 
 # Special:Categories
 'categories' => 'Катеґорії',
@@ -1860,7 +1907,7 @@ $1',
 Невказаны суть то [[Special:UnusedCategories|нехоснованы катеґорії]].
 Посмотьте ся тыж на [[Special:WantedCategories|жаданы катеґорії]].',
 'categoriesfrom' => 'Вказати сторінкы, што ся зачінають на:',
-'special-categories-sort-count' => 'Ñ\83поÑ\80Ñ\8fдковаÑ\82и Ð¿Ð¾Ð´Ð»Ñ\8f Ð¼Ð½Ð¾Ð¶ÐµÑ\81Ñ\82ва',
+'special-categories-sort-count' => 'Ñ\83поÑ\80Ñ\8fдковаÑ\82и Ð·Ð° ÐºÑ\96лÑ\8cкоÑ\81Ñ\82Ñ\91в',
 'special-categories-sort-abc' => 'упорядковати за алфавітом',
 
 # Special:DeletedContributions
@@ -1921,7 +1968,7 @@ $1',
 # E-mail user
 'mailnologin' => 'Без адресы одосланя',
 'mailnologintext' => 'Кідь хочете посылати ел. пошту іншым хоснователям, мусите ся [[Special:UserLogin|приголосити]] і мати платну адресу ел. пошты в своїм [[Special:Preferences|наставлїню]].',
-'emailuser' => 'Послати е-маіл тому хоснователёви',
+'emailuser' => 'Послати імейл тому хоснователёви',
 'emailpage' => 'Пошлийте е-пошту',
 'emailpagetext' => 'Помочов ниже зображеного формуларя можете тому хоснователёви послати повідомлїня ел. поштов.
 Адреса ел. пошты, котру мате зазначену в [[Special:Preferences|наставлїня]],ся обявить як адреса одосылателя пошты, жебы вам адресат міг одповісти прямо.',
@@ -1931,7 +1978,7 @@ $1',
 'usermaildisabledtext' => 'Не маєте право одосылати ел. пошту іншым хоснователям той вікі',
 'noemailtitle' => 'Без адресы ел. пошты',
 'noemailtext' => 'Тот хоснователь не зазначів платну адресу ел. пошты.',
-'nowikiemailtitle' => 'Ел. пошта не є доволена',
+'nowikiemailtitle' => 'Ð\95л. Ð¿Ð¾Ñ\88Ñ\82а Ð½Ðµ Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð°',
 'nowikiemailtext' => 'Тот хоснователь собі не желать діставати пошту од іншых хоснователїв.',
 'emailnotarget' => 'Неекзістуюче або некоректне імя хоснователя.',
 'emailtarget' => 'Уведьте імя хоснователя-адресата',
@@ -1987,7 +2034,7 @@ $1',
 # Displayed when you click the "watch" button and it is in the process of watching
 'watching' => 'Придаваня до списку слїдованя...',
 'unwatching' => 'Одобратя зо списку слїдованя...',
-'watcherrortext' => 'При змінї слїдованой сторінкы „$1“ дішло ку хыбі.',
+'watcherrortext' => 'При змінї слїдованой сторінкы „$1“ ся стала хыба.',
 
 'enotif_mailer' => 'Засылач нотіфікацій {{grammar:2sg|{{SITENAME}}}}',
 'enotif_reset' => 'Означіти вшытко як навщівене',
@@ -2030,7 +2077,7 @@ $UNWATCHURL
 
 # Delete
 'deletepage' => 'Змазати сторінку',
-'confirm' => 'Підтверджіня',
+'confirm' => 'Потверджіня',
 'excontent' => 'обсяг быв: „$1“',
 'excontentauthor' => 'обсяг быв: „$1“ (і єдиным приспівателём быв „[[Special:Contributions/$2|$2]]“)',
 'exbeforeblank' => 'обсяг перед выпорожнїнём быв: „$1“',
@@ -2039,7 +2086,7 @@ $UNWATCHURL
 'delete-legend' => 'Вымазати',
 'historywarning' => "'''Варованя:''' Сторінка, котру хочете змазати, має історію з приближно $1 {{plural:$1|ревізії|ревізіями}}:",
 'confirmdeletetext' => 'Рыхтуєте ся вымазати сторінку і вшыткы єй лоґы едітовань.
-Просиме Вас, підтвердьте, же справды тото хочете зробити, повно розумієте наслїдкы і же робите тото в одповідности з [[{{MediaWiki:Policy-url}}|правилами]].',
+Просиме Вас, потвердьте, же справды тото хочете зробити, повно розумієте наслїдкы і же робите тото в одповідности з [[{{MediaWiki:Policy-url}}|правилами]].',
 'actioncomplete' => 'Дїя выконана',
 'actionfailed' => 'Операція ся не вдала',
 'deletedtext' => '"$1" было змазане.
@@ -2056,13 +2103,15 @@ $UNWATCHURL
 ** Порушїня авторьскых прав
 ** Вандалізм',
 'delete-edit-reasonlist' => 'Едітовати причіны вымазаня',
-'delete-toobig' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ð¼Ð°Ñ\94 Ð²ÐµÐ»Ð¸ÐºÑ\83 Ñ\96Ñ\81Ñ\82оÑ\80Ñ\96Ñ\8e ÐµÐ´Ñ\96Ñ\82ованÑ\8f, Ñ\87еÑ\80ез $1 {{plural:$1|веÑ\80зÑ\96Ñ\97|веÑ\80зÑ\96й|веÑ\80зÑ\96й}}. Ð\9cазанÑ\8f Ñ\82акÑ\8bÑ\85 Ñ\81Ñ\82оÑ\80Ñ\96нок Ñ\94 Ð¾Ð±Ð¼ÐµÐ´Ð¶ÐµÐ½Ð¾, Ð¶ÐµÐ±Ñ\8b Ñ\81Ñ\8f Ð¿ÐµÑ\80едÑ\96Ñ\88ло нехоченому нарушіню {{grammar:2sg|{{SITENAME}}}}.',
+'delete-toobig' => 'ТоÑ\82а Ñ\81Ñ\82оÑ\80Ñ\96нка Ð¼Ð°Ñ\94 Ð²ÐµÐ»Ð¸ÐºÑ\83 Ñ\96Ñ\81Ñ\82оÑ\80Ñ\96Ñ\8e ÐµÐ´Ñ\96Ñ\82ованÑ\8f, Ñ\87еÑ\80ез $1 {{plural:$1|веÑ\80зÑ\96Ñ\97|веÑ\80зÑ\96й|веÑ\80зÑ\96й}}. Ð\9cазанÑ\8f Ñ\82акÑ\8bÑ\85 Ñ\81Ñ\82оÑ\80Ñ\96нок Ñ\94 Ð¾Ð±Ð¼ÐµÐ´Ð¶ÐµÐ½Ð¾, Ð¶ÐµÐ±Ñ\8b Ñ\81Ñ\8f Ð·Ð°Ð±Ð¾Ñ\80онило нехоченому нарушіню {{grammar:2sg|{{SITENAME}}}}.',
 'delete-warning-toobig' => 'Тота сторінка має велику історію едітацій, через $1 {{plural:$1|верзії|верзій|верзій}}. Мазаня такых сторінок може нарушыти датабазовы операцім {{grammar:2sg|{{SITENAME}}}}; мерькуйте.',
 
 # Rollback
 'rollback' => 'Вернути назад едітованя',
 'rollback_short' => 'Вернути назад',
 'rollbacklink' => 'вернути назад',
+'rollbacklinkcount' => 'вернутя $1 {{PLURAL:$1|едітованя|едітовань}} назад',
+'rollbacklinkcount-morethan' => 'вернутя бівше як $1 {{PLURAL:$1|едітованя|едітовань}} назад',
 'rollbackfailed' => 'Не годно было ся вернути назад',
 'cantrollback' => 'Не годен вернути послїднє едітованя, бо послїднїй приспіватель є єдиным автором той сторінкы.',
 'alreadyrolled' => 'Не годен вернути послїднє едітованя [[:$1]] од хоснователя [[User:$2|$2]] ([[User talk:$2|діскузія]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]), бо дахто друхый уже сторінку едітовав або вернув тоту зміну назад.
@@ -2080,9 +2129,9 @@ $UNWATCHURL
 Стисните клапку „назад“, обновте сторінку, з котрой сьте пришли і спробуйте то знову;',
 
 # Protect
-'protectlogpage' => 'Лоґ охраны',
-'protectlogtext' => 'Ниже є уведеный список вшыткых замків і сторінок.
\9fоÑ\81моÑ\82Ñ\8c [[Special:ProtectedPages|Ñ\81пиÑ\81ок Ñ\85Ñ\80аненых сторінок]]',
+'protectlogpage' => 'Лоґ сокочіня',
+'protectlogtext' => 'Ð\9dиже Ñ\94 Ñ\83ведженÑ\8bй Ñ\81пиÑ\81ок Ð²Ñ\88Ñ\8bÑ\82кÑ\8bÑ\85 Ð·Ð°Ð¼ÐºÑ\96в Ñ\96 Ñ\81Ñ\82оÑ\80Ñ\96нок.
\9fоÑ\81моÑ\82Ñ\8c [[Special:ProtectedPages|Ñ\81пиÑ\81ок Ñ\81окоÑ\87еных сторінок]]',
 'protectedarticle' => 'замыкать "[[$1]]"',
 'modifiedarticleprotection' => 'зміненa рівень охраны сторінкы «[[$1]]»',
 'unprotectedarticle' => 'знята охрана з "[[$1]]"',
@@ -2092,12 +2141,12 @@ $UNWATCHURL
 'prot_1movedto2' => '«[[$1]]» переменована на «[[$2]]»',
 'protect-badnamespace-title' => 'Незамыкательный простор назв',
 'protect-badnamespace-text' => 'Сторінкы в тім просторї назв не годен замыкати.',
-'protect-legend' => 'Підтвердити замкнутя',
+'protect-legend' => 'Потвердити замкнутя',
 'protectcomment' => 'Причіна:',
 'protectexpiry' => 'Кінчіть:',
 'protect_expiry_invalid' => 'Неправилный час укончіня',
 'protect_expiry_old' => 'Час страты платности є в минулости.',
-'protect-unchain-permissions' => 'Ð\97пÑ\80иÑ\81Ñ\82Ñ\83пниÑ\82и далшы наставлїня замку',
+'protect-unchain-permissions' => 'Ð\9eÑ\82воÑ\80иÑ\82и Ð¿Ñ\80иÑ\81Ñ\82Ñ\83п Ð½Ð° далшы наставлїня замку',
 'protect-text' => "Ту можете видїти і змінити рівень охраны сторінкы '''$1'''.",
 'protect-locked-blocked' => "Не можете мінити наставлїня замків покы сьте заблокованый. Сучасне наставлїня про тоту сторінку є: '''$1''':",
 'protect-locked-dblock' => "Наставлїня замків ся не дасть змінити про замкнуту датабазу.
@@ -2105,14 +2154,14 @@ $UNWATCHURL
 'protect-locked-access' => "Ваше конто немає права мінити рівень охраны сторінкы.
 Моменталны наставлїня про сторінку : '''$1''':",
 'protect-cascadeon' => 'Тота сторінка є теперь хранена, бо є загорнута {{PLURAL:$1|до зазначеной сторінкы ніже, на яку|до ниже зазначеных сторінок, на якы}} становлена каскадова охрана. Вы можете змінити рівень охраны той сторінкы, але тото не буде впливати на каскадову охрану.',
-'protect-default' => 'Доволити вшыткым хоснователям',
+'protect-default' => 'Ð\94озволиÑ\82и Ð²Ñ\88Ñ\8bÑ\82кÑ\8bм Ñ\85оÑ\81новаÑ\82елÑ\8fм',
 'protect-fallback' => 'Портрібны "$1" права',
 'protect-level-autoconfirmed' => 'Блоковати новых і незареґістрованых хоснователїв',
 'protect-level-sysop' => 'Лем адміністраторы',
 'protect-summary-cascade' => 'каскадовый',
 'protect-expiring' => 'кінчіть $1 (UTC)',
 'protect-expiring-local' => 'кінчіть ся $1',
-'protect-expiry-indefinite' => 'до Ð¾Ð´Ð²Ð¾Ð»Ð°Ð½Ñ\8f',
+'protect-expiry-indefinite' => 'навÑ\81е (до Ð¿Ð¾ÐºÐ»Ð¸ÐºÐ°Ð½Ñ\8f)',
 'protect-cascade' => 'Хранити сторінкы вложены до той сторінкы (каскадова охрана)',
 'protect-cantedit' => 'Вы не можете змінити рівень охороны той сторінкы, тому што вы не маєте прав про єй едітованя.',
 'protect-othertime' => 'Іншый час:',
@@ -2137,7 +2186,7 @@ $UNWATCHURL
 'restriction-edit' => 'Едітованя',
 'restriction-move' => 'Переменовати',
 'restriction-create' => 'Вытвориня',
-'restriction-upload' => 'Ð\9dагÑ\80аваня файлів',
+'restriction-upload' => 'Ð\97аладовованя файлів',
 
 # Restriction levels
 'restriction-level-sysop' => 'замкнуте',
@@ -2162,29 +2211,31 @@ $UNWATCHURL
 'undeleterevision-missing' => 'Неправилна або хыбляча ревізія. Може маєте планый одказ, або ревізія была обновлена ці одстранена з архіву.',
 'undelete-nodiff' => 'Не найджена жадна попередня верзія.',
 'undeletebtn' => 'Обновити',
-'undeletelink' => 'відїти/обновити',
-'undeleteviewlink' => 'відїти',
+'undeletelink' => 'видїти/обновити',
+'undeleteviewlink' => 'видїти',
 'undeletereset' => 'Ресетовати',
 'undeleteinvert' => 'Інвертовати селекцію',
 'undeletecomment' => 'Причіна:',
 'undeletedrevisions' => '{{PLURAL:$1|Обновлена $1 верзія|Обновлены $1 верзії|Обновленых $1 верзій}}',
 'undeletedrevisions-files' => '{{PLURAL:$1|Обновлена єдна верзія|Обновлены $1 верзії|Обновленых $1 верзій}} і $2 {{PLURAL:$2|файл|файлы|файлів}}.',
 'undeletedfiles' => '{{PLURAL:$1|обновленый $1 файл|обновлены $1 файлы|обновленых $1 файлів}}',
-'cannotundelete' => 'Ð\9eбновлÑ\97нÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило; правдоподобно  дахто другый обновив сторінку скоре як вы.',
+'cannotundelete' => 'Ð\9eбновлÑ\97нÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°ло; правдоподобно  дахто другый обновив сторінку скоре як вы.',
 'undeletedpage' => "'''$1 была обновлена'''
 
 Запис о послїднїх мазанях і обновлїнях найдете в  [[Special:Log/delete|книзї змазаных сторінок]].",
 'undelete-header' => 'Видьте недавно змазаны сторінкы в [[Special:Log/delete|книзї змазаных сторінок]].',
+'undelete-search-title' => 'Гляданя змазаных сторінок',
 'undelete-search-box' => 'Гляданя вымазаных сторінок',
 'undelete-search-prefix' => 'Вказати сторінкы што ся почінають з',
 'undelete-search-submit' => 'Найти',
 'undelete-no-results' => 'Пожадавцї жадны змазаны сторінкы не одповідають.',
 'undelete-filename-mismatch' => 'Не годен обновити верзію файлу з часовов значков $1: назва файлу не одповідать',
 'undelete-bad-store-key' => 'Не годен обновити верзію файлу з часовов значков $1:  файл педед змазанём хыбив',
-'undelete-cleanup-error' => 'Хыба почас мазаня нехоснованого архівного файлу „$1“.',
-'undelete-missing-filearchive' => 'Не подарило ся обновити файл архіву з ідентіфікаціёв $1, протоже не є в датабазї. Може уже быв обновленый.',
-'undelete-error-short' => 'Хыба почас обновованя файлу: $1',
-'undelete-error-long' => 'Взникла хыба почас обновованя файлу:
+'undelete-cleanup-error' => 'Хыба мазаня нехоснованого архівного файлу „$1“.',
+'undelete-missing-filearchive' => 'Не вдало ся обновити файл архіву з ідентіфікаціёв $1, бо не є в датабазї. Може же уж быв обновленый.',
+'undelete-error' => 'Хыба обновлїня сторінкы',
+'undelete-error-short' => 'Хыба обновлїня файлу: $1',
+'undelete-error-long' => 'Выникла хыба під час обновлїня файлу:
 
 $1',
 'undelete-show-file-confirm' => 'На певно собі хочете посмотрити змазану ревізію файлу „<nowiki>$1</nowiki>“ з $2, $3?',
@@ -2203,17 +2254,17 @@ $1',
 'contributions-title' => 'Приспевок хоснователя $1',
 'mycontris' => 'Мої приспівкы',
 'contribsub2' => 'Приспевок $1 ($2)',
-'nocontribs' => 'Ð\9dенайдженÑ\8b Ð¶Ð°Ð´Ð½Ñ\8b Ð·Ð¼Ñ\96нÑ\8b Ð¿Ð¾Ð´Ð»Ñ\8f Ñ\82Ñ\8bÑ\85 ÐºÑ\80Ñ\96Ñ\82еÑ\80Ñ\96й.',
+'nocontribs' => 'Ð\9dенайдженÑ\8b Ð¶Ð°Ð´Ð½Ñ\8b Ð·Ð¼Ñ\96нÑ\8b Ð·Ð° Ñ\82Ñ\8bма ÐºÑ\80Ñ\96Ñ\82еÑ\80Ñ\96Ñ\8fми.',
 'uctop' => ' (послїдня)',
 'month' => 'Од місяця (і скоре):',
 'year' => 'Од року (і скоре):',
 
-'sp-contributions-newbies' => 'Вказати приспевкы лем новых конт',
+'sp-contributions-newbies' => 'Вказати приспівкы лем новых конт',
 'sp-contributions-newbies-sub' => 'Новы хоснователї',
 'sp-contributions-newbies-title' => 'Приспівкы новый хоснователїв',
 'sp-contributions-blocklog' => 'Лоґ блокованя',
 'sp-contributions-deleted' => 'вымазаны приспевкы хоснователя',
-'sp-contributions-uploads' => 'нагÑ\80аванÑ\8f',
+'sp-contributions-uploads' => 'заладованÑ\8b Ñ\84айлÑ\8b',
 'sp-contributions-logs' => 'лоґы',
 'sp-contributions-talk' => 'діскузія',
 'sp-contributions-userrights' => 'Справа хосновательскых прав',
@@ -2221,8 +2272,8 @@ $1',
 Послїднїй запис в лоґах блоковань є такый:',
 'sp-contributions-blocked-notice-anon' => 'Тота IP адреса є теперь блокована.
 Послїднїй запис в лоґах блоковань є такый:',
-'sp-contributions-search' => 'Глядати приспевкы',
-'sp-contributions-username' => 'IP-адреса або мено хоснователя:',
+'sp-contributions-search' => 'Глядати приспівкы',
+'sp-contributions-username' => 'IP-адреса або імя хоснователя:',
 'sp-contributions-toponly' => 'Вказати лем актуалны ревізії',
 'sp-contributions-submit' => 'Найти',
 
@@ -2268,7 +2319,7 @@ $1',
 ** Знеужываня веце конт
 ** Невгодне мено хоснователя',
 'ipb-hardblock' => 'Заборонити приголошеным хоснователям едітовати з той IP-адресы',
-'ipbcreateaccount' => 'Не доволити реґістрацію новых хоснователїв',
+'ipbcreateaccount' => 'Ð\9dе Ð´Ð¾Ð·Ð²Ð¾Ð»Ð¸Ñ\82и Ñ\80еÒ\91Ñ\96Ñ\81Ñ\82Ñ\80аÑ\86Ñ\96Ñ\8e Ð½Ð¾Ð²Ñ\8bÑ\85 Ñ\85оÑ\81новаÑ\82елÑ\97в',
 'ipbemailban' => 'Заборонити хоснователёви посылати ел. пошту',
 'ipbenableautoblock' => 'Автоматічно блоковати IP адресы хоснованы тым хоснователём',
 'ipbsubmit' => 'Заблоковати',
@@ -2314,11 +2365,11 @@ $1',
 'ipblocklist-submit' => 'Глядати',
 'ipblocklist-localblock' => 'Локалне блокованя',
 'ipblocklist-otherblocks' => '{{PLURAL:$1|Інше блокованя|Іншы блокованя}}',
-'infiniteblock' => 'до Ð¾Ð´Ð²Ð¾Ð»Ð°Ð½Ñ\8f',
+'infiniteblock' => 'навÑ\81е (до Ð¿Ð¾ÐºÐ»Ð¸ÐºÐ°Ð½Ñ\8f)',
 'expiringblock' => 'до $1, $2',
 'anononlyblock' => 'лем анонімы',
 'noautoblockblock' => 'без автоблокованя',
-'createaccountblock' => 'вÑ\8bÑ\82ваÑ\80Ñ\8fнÑ\8f ÐºÐ¾Ð½Ñ\82 Ð½Ðµ Ñ\94 Ð¿Ð¾волене',
+'createaccountblock' => 'вÑ\8bÑ\82воÑ\80Ñ\91ванÑ\8f ÐºÐ¾Ð½Ñ\82 Ð½Ðµ Ð´Ð¾Ð·волене',
 'emailblock' => 'е-маіл блокованый',
 'blocklist-nousertalk' => 'не може едітовати властну сторінку діскузії',
 'ipblocklist-empty' => 'Список блоковань є порожнїй.',
@@ -2340,13 +2391,13 @@ $1',
 Смотьте тыж [[Special:BlockList|список вшыткых чінных блоковань]].',
 'unblocklogentry' => 'одблоковав $1',
 'block-log-flags-anononly' => 'лем анонімны хоснователї',
-'block-log-flags-nocreate' => 'вÑ\8bÑ\82ваÑ\80Ñ\8fнÑ\8f ÐºÐ¾Ð½Ñ\82 Ð½Ðµ Ð¿Ð¾волене',
+'block-log-flags-nocreate' => 'вÑ\8bÑ\82воÑ\80Ñ\91ванÑ\8f ÐºÐ¾Ð½Ñ\82 Ð½Ðµ Ð´Ð¾Ð·волене',
 'block-log-flags-noautoblock' => 'автоматічне блокованя выпнуте',
 'block-log-flags-noemail' => 'е-маіл блокованый',
 'block-log-flags-nousertalk' => 'не може едітовати властну сторінку діскузії',
 'block-log-flags-angry-autoblock' => 'росшырене автоматічне блокованя выпнуте',
 'block-log-flags-hiddenname' => 'мено хоснователя сховане',
-'range_block_disabled' => 'Ð\91локованÑ\8f Ñ\80оÑ\81Ñ\81Ñ\8fгÑ\96в IP-адÑ\80еÑ\81 Ð½Ðµ Ñ\94 Ð¿Ð¾Ð²Ð¾Ð»ÐµÐ½Ðµ',
+'range_block_disabled' => 'Ð\91локованÑ\8f Ñ\80оÑ\81Ñ\81Ñ\8fгÑ\96в IP-адÑ\80еÑ\81 Ð½Ðµ Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ðµ.',
 'ipb_expiry_invalid' => 'Неплатный час експірації.',
 'ipb_expiry_temp' => 'Блокованя схованых мен хоснователїв бы мало быти тырвале.',
 'ipb_hide_invalid' => 'Тото конто ся не дасть затаїти; може має дуже много едітацій.',
@@ -2357,30 +2408,30 @@ $1',
 'ipb_cant_unblock' => 'Хыба: Блокованя з ID $1 не было найджене. Хоснователь уж може быв одблокованый.',
 'ipb_blocked_as_range' => 'Хыба: IP-адреса $1 не є блокована прямо а так єй не є можне одблоковати. Є частёв заблокованого россягу $2, котрый може быти одблокованый.',
 'ip_range_invalid' => 'Неплатный IP россяг.',
-'ip_range_toolarge' => 'Ð\91локованÑ\8f Ñ\80оÑ\81Ñ\81Ñ\8fгÑ\96в Ð²ÐµÐºÑ\88Ñ\8bÑ\85 Ñ\8fк  /$1 Ð½Ðµ Ñ\94 Ð¿Ð¾волене.',
+'ip_range_toolarge' => 'Ð\91локованÑ\8f Ñ\80оÑ\81Ñ\81Ñ\8fгÑ\96в Ð²ÐµÐºÑ\88Ñ\8bÑ\85 Ñ\8fк  /$1 Ð½Ðµ Ñ\94 Ð´Ð¾Ð·волене.',
 'blockme' => 'Заблокуй ня',
 'proxyblocker' => 'Блокованя проксі',
 'proxyblocker-disabled' => 'Тота фунція є выпнута.',
 'proxyblockreason' => 'Ваша IP-адреса была заблокована, зато же фунґує як отвореный проксі сервер. 
\9aонÑ\82акÑ\82Ñ\83йÑ\82е Ñ\81вого Ð\86нÑ\82еÑ\80неÑ\82-пÑ\80овайдеÑ\80а Ð°Ð±Ð¾ Ñ\82еÑ\85нÑ\96Ñ\87нÑ\83 Ð¿Ñ\96дпоÑ\80Ñ\83 Ñ\96 Ñ\96нÑ\84оÑ\80мÑ\83йÑ\82е Ñ\97Ñ\85 Ð¾ Ñ\82Ñ\96м Ñ\81еÑ\80Ñ\91знÑ\96м Ð±ÐµÐ·печностнім проблемі.',
\9aонÑ\82акÑ\82Ñ\83йÑ\82е Ñ\81вого Ð\86нÑ\82еÑ\80неÑ\82-пÑ\80овайдеÑ\80а Ð°Ð±Ð¾ Ñ\82еÑ\85нÑ\96Ñ\87нÑ\83 Ð¿Ñ\96дпоÑ\80Ñ\83 Ñ\96 Ñ\96нÑ\84оÑ\80мÑ\83йÑ\82е Ñ\97Ñ\85 Ð¾ Ñ\82Ñ\96м Ñ\81еÑ\80Ñ\8cÑ\91знÑ\96м Ð±ÐµÑ\81печностнім проблемі.',
 'proxyblocksuccess' => 'Готово.',
 'sorbsreason' => 'Ваша IP-адреса є веджена як отвореный проксі в DNSBL.',
 'sorbs_create_account_reason' => 'Ваша IP-адреса є веджена як одкрытый проксі в DNSBL. З той адресы собі не можете створити конто.',
 'cant-block-while-blocked' => 'Не можете блоковати іншых хоснователїв, кідь сьте сам заблокованый(а).',
 'cant-see-hidden-user' => 'Хоснователь, котрого хочете заблоковати, уж быв заблокованый і схованый. Кідьже не маєте права hideuser, не можете собі наставлїня блокованя того хоснователя посмотрити ани го змінити.',
 'ipbblocked' => 'Не можете блоковати або одблоковати іншых хоснователїв, {{GENDER:|сам|сама|сам}} сьте {{GENDER:|заблокованый|заблокована|заблокованый}}',
-'ipbnounblockself' => 'Не маєте доволене одблоковати {{GENDER:|сам|сама|сам}} себе',
+'ipbnounblockself' => 'Ð\9dе Ð¼Ð°Ñ\94Ñ\82е Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ðµ Ð¾Ð´Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ñ\82и {{GENDER:|Ñ\81ам|Ñ\81ама|Ñ\81ам}} Ñ\81ебе',
 
 # Developer tools
 'lockdb' => 'Замкнути датабазу',
 'unlockdb' => 'Одомкнути датабазу',
-'lockdbtext' => 'Кідь замкнете датабазу, знеможните другым едітовати, управляти наставлїня, слїдованы сторінкы ітд. Підтвердьте, же то справды хочете зробити і же одомкнете датабазу такой по оправах.',
-'unlockdbtext' => 'Кідь одомкнете датабазу, уможните другым едітовати, управляти наставлїня, слїдованы сторінкы ітд. Підтвердьте, же то хочете справды зробити.',
+'lockdbtext' => 'Кідь замкнете датабазу, знеможните другым едітовати, управляти наставлїня, слїдованы сторінкы ітд. Потвердьте, же то справды хочете зробити і же одомкнете датабазу такой по оправах.',
+'unlockdbtext' => 'Кідь одомкнете датабазу, уможните другым едітовати, управляти наставлїня, слїдованы сторінкы ітд. Потвердьте, же то хочете справды зробити.',
 'lockconfirm' => 'Гей, справды хочу замкнути датабазу.',
 'unlockconfirm' => 'Гей, справды хочу одомкнути датабазу.',
 'lockbtn' => 'Замкнути датабазу',
 'unlockbtn' => 'Одомкнути датабазу',
-'locknoconfirm' => 'Не было означене поличко підтверджіня.',
+'locknoconfirm' => 'Не было означене поличко потверджіня.',
 'lockdbsuccesssub' => 'Датабаза замкнута',
 'unlockdbsuccesssub' => 'Датабаза одомкнута',
 'lockdbsuccesstext' => 'Датабаза {{grammar:2sg|{{SITENAME}}}} была успішно замкнута.
@@ -2397,7 +2448,7 @@ $1',
 Стара назва стане перенапрямлинём на нову назву.
 Можете автоматично обновити напрямлиня на стару назву.
 Кідь вы тото не зробите, просиме Вас, перевірте наявність [[Special:DoubleRedirects|подвойных]] ці [[Special:BrokenRedirects|розорваных]] напрямлїнь.
\92Ñ\8b Ð·Ð¾Ð´Ð¿Ð¾Ð²Ñ\96даÑ\94те за то, жебы одказы і надале вказовали там, де мають.
\92Ñ\8b Ð¾Ð´Ð¿Ð¾Ð²Ñ\96дате за то, жебы одказы і надале вказовали там, де мають.
 
 Уважте, же сторінка '''не''' буде переменована, кідь сторінка з новов назвов уж екзістує, окрем того, коли она порожня або є напрямлїнём, а лоґ єй едітовань порожнїй.
 То значіть, же вы можете вернути сторінцї стару назву, кідь вы переменовали єй помылково, але вы не можете переписати екзістуючу сторінку.
@@ -2448,7 +2499,7 @@ $1',
 'movepage-page-exists' => 'Сторінка $1 уж екзістує і не може быти автоматічно переписана.',
 'movepage-page-moved' => 'Сторінка $1 была переменована на $2.',
 'movepage-page-unmoved' => 'Сторінка $1 не може быти переменована на $2.',
-'movepage-max-pages' => '{{PLURAL:$1|Ð\91Ñ\8bла Ð¿ÐµÑ\80еменована Ð¼Ð°ÐºÑ\81Ñ\96мално Ð¿Ð¾Ð²Ð¾Ð»ÐµÐ½Ð° Ñ\94дна Ñ\81Ñ\82оÑ\80Ñ\96нка|Ð\91Ñ\8bли Ð¿ÐµÑ\80еменованÑ\8b Ð¼Ð°ÐºÑ\81Ñ\96мално Ð¿Ð¾Ð²Ð¾Ð»ÐµÐ½Ñ\8b $1 Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b\91Ñ\8bло Ð¿ÐµÑ\80еменоване Ð¼Ð°ÐºÑ\81Ñ\96мално Ð¿Ð¾воленых $1 сторінок}}, веце їх уж автоматічно переменованых не буде.',
+'movepage-max-pages' => '{{PLURAL:$1|Ð\91Ñ\8bла Ð¿ÐµÑ\80еменована Ð¼Ð°ÐºÑ\81Ñ\96мално Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð° Ñ\94дна Ñ\81Ñ\82оÑ\80Ñ\96нка|Ð\91Ñ\8bли Ð¿ÐµÑ\80еменованÑ\8b Ð¼Ð°ÐºÑ\81Ñ\96мално Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ñ\8b $1 Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b\91Ñ\8bло Ð¿ÐµÑ\80еменоване Ð¼Ð°ÐºÑ\81Ñ\96мално Ð´Ð¾Ð·воленых $1 сторінок}}, веце їх уж автоматічно переменованых не буде.',
 'movelogpage' => 'Лоґ переменовань',
 'movelogpagetext' => 'Тото є список вшыткых переменованый сторінок.',
 'movesubpage' => '{{PLURAL:$1|Підсторінка|Підсторінкы}}',
@@ -2514,11 +2565,11 @@ $1',
 Кідь хочете приспівати ку локалізації софтверу MediaWiki, навщівте [//www.mediawiki.org/wiki/Localisation локалізачну сторінку на mediawiki.org] і [//translatewiki.net сервер server translatewiki.net].',
 'allmessagesnotsupportedDB' => '{{ns:special}}:AllMessages не є підпороване, бо wgUseDatabaseMessages є выпнуте.',
 'allmessages-filter-legend' => 'Філтер',
-'allmessages-filter' => 'ФÑ\96лÑ\82еÑ\80 Ð¿Ð¾Ð´Ð»Ñ\8f Ñ\81Ñ\82авÑ\83:',
+'allmessages-filter' => 'ФÑ\96лÑ\82еÑ\80 Ð·Ð° Ñ\81Ñ\82аном:',
 'allmessages-filter-unmodified' => 'Незмінено',
 'allmessages-filter-all' => 'Вшыткы',
 'allmessages-filter-modified' => 'Змінено',
-'allmessages-prefix' => 'ФÑ\96лÑ\82еÑ\80 Ð¿Ð¾Ð´Ð»Ñ\8f Ð¿Ñ\80еÑ\84Ñ\96кÑ\81Ñ\83:',
+'allmessages-prefix' => 'ФÑ\96лÑ\82еÑ\80 Ð·Ð° Ð¿Ñ\80еÑ\84Ñ\96кÑ\81ом:',
 'allmessages-language' => 'Язык:',
 'allmessages-filter-submit' => 'Выконати',
 
@@ -2527,7 +2578,7 @@ $1',
 'filemissing' => 'Файл хыбить',
 'thumbnail_error' => 'Хыба створїня нагляду: $1',
 'djvu_page_error' => 'Сторінка DjVu мімо россяг',
-'djvu_no_xml' => 'СÑ\82воÑ\80Ñ\97нÑ\8f XML Ð¿Ñ\80о Ñ\84айл DjVu Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило.',
+'djvu_no_xml' => 'СÑ\82воÑ\80Ñ\97нÑ\8f XML Ð¿Ñ\80о Ñ\84айл DjVu Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°ло.',
 'thumbnail-temp-create' => 'Дочасный файл нагляду негодно было створити',
 'thumbnail-dest-create' => 'Нагляд не годно было уложыти на призначене місце',
 'thumbnail_invalid_params' => 'Неплатный параметер нагляду',
@@ -2547,28 +2598,28 @@ $1',
 'import-interwiki-namespace' => 'Цілёвый простор назв:',
 'import-upload-filename' => 'Назва файлу:',
 'import-comment' => 'Коментарь:',
-'importtext' => 'Ð\9fÑ\80оÑ\81име Ð\92аÑ\81, ÐµÐºÑ\81поÑ\80Ñ\82Ñ\83йÑ\82е Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\83 Ð· Ñ\96нÑ\88ой Ð²Ñ\96кÑ\96 Ð¿Ð¾Ð¼Ð¾Ñ\87ов [[Special:Export|Ñ\96нÑ\88Ñ\82Ñ\80Ñ\83менÑ\82Ñ\83 Ð½Ð° ÐµÐºÑ\81поÑ\80Ñ\82]], Ñ\83ложÑ\82е Ñ\84айл Ð½Ð° Ð²Ð°Ñ\88 Ð´Ñ\96Ñ\81к Ð° Ð¿Ð¾Ñ\82Ñ\96м Ð³Ð¾ Ð½Ð°Ð³Ñ\80айÑ\82е Ñ\82Ñ\83.',
+'importtext' => 'Ð\9fÑ\80оÑ\81име Ð\92аÑ\81, ÐµÐºÑ\81поÑ\80Ñ\82Ñ\83йÑ\82е Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\83 Ð· Ñ\96нÑ\88ой Ð²Ñ\96кÑ\96 Ð¿Ð¾Ð¼Ð¾Ñ\87ов [[Special:Export|Ñ\96нÑ\88Ñ\82Ñ\80Ñ\83менÑ\82Ñ\83 Ð½Ð° ÐµÐºÑ\81поÑ\80Ñ\82]], Ñ\83ложÑ\82е Ñ\84айл Ð½Ð° Ð²Ð°Ñ\88 Ð´Ñ\96Ñ\81к Ð° Ð¿Ð¾Ñ\82Ñ\96м Ð³Ð¾ Ð·Ð°Ð»Ð°Ð´Ñ\83йÑ\82е Ð³ÐµÐ².',
 'importstart' => 'Імпорт сторінок…',
 'import-revision-count' => '$1 {{PLURAL:$1|ревізія|ревізії|ревізій}}',
 'importnopages' => 'Не є што імпортовати.',
 'imported-log-entries' => '{{PLURAL:$1|Наімпортованый 1 протоколовачій запис|Наімпортованы $1 протоколовачі записы|Наімпортованых $1 протоколовачіх записів}}.',
-'importfailed' => 'Ð\86мпоÑ\80Ñ\82 Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ив: $1',
+'importfailed' => 'Ð\86мпоÑ\80Ñ\82 Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°в: $1',
 'importunknownsource' => 'Незнамый тіп  імпортованой сторінкы',
 'importcantopen' => 'Не дало ся отворити файл імпорту',
 'importbadinterwiki' => 'Неплатный одказ інтервікі',
 'importnotext' => 'Порожнїй або жаден текст',
 'importsuccess' => 'Імпорт сконченый!',
 'importhistoryconflict' => 'Екзістує конфлікт міджі історіямі верзії (може тота сторінка уж была імпортована скоре)',
-'importnosources' => 'Ð\9dе Ð±Ñ\8bло Ð²Ñ\8bбÑ\80ане Ð¶Ñ\80Ñ\96дло Ñ\96мпоÑ\80Ñ\82Ñ\83 Ð¼Ñ\96джÑ\96 Ð²Ñ\96кÑ\96, Ð¿Ñ\80Ñ\8fме Ð½Ð°Ñ\87Ñ\96Ñ\82аня історії змін є выпнуте.',
-'importnofile' => 'Ð\9dе Ð±Ñ\8bв Ð½Ð°Ñ\87Ñ\96Ñ\82аный файл імпорту.',
-'importuploaderrorsize' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило Ñ\81Ñ\8f Ð½Ð°Ñ\87Ñ\96Ñ\82ати файл імпорту. Розмір файлу перевышує становлену меджу.',
-'importuploaderrorpartial' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило Ñ\81Ñ\8f Ð½Ð°Ñ\87Ñ\96Ñ\82аÑ\82и Ñ\96мпоÑ\80Ñ\82нÑ\8bй Ñ\84айл. Ð¤Ð°Ð¹Ð» Ð±Ñ\8bв Ð½Ð°Ñ\87Ñ\96Ñ\82анÑ\8bй Ð»ÐµÐ¼ Ñ\87аÑ\81Ñ\82оÑ\87но.',
-'importuploaderrortemp' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило Ñ\81Ñ\8f Ð½Ð°Ñ\87Ñ\96Ñ\82ати імпортный файл. Не є к діспозіції дочасный адресарь.',
-'import-parse-failure' => 'Хыба почас імпорту XML',
+'importnosources' => 'Ð\9dе Ð±Ñ\8bло Ð²Ñ\8bбÑ\80ане Ð¶Ñ\80Ñ\96дло Ñ\96мпоÑ\80Ñ\82Ñ\83 Ð¼Ñ\96джÑ\96 Ð²Ñ\96кÑ\96, Ð´Ñ\96Ñ\80екÑ\82 Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аня історії змін є выпнуте.',
+'importnofile' => 'Ð\9dе Ð±Ñ\8bв Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аный файл імпорту.',
+'importuploaderrorsize' => 'Ð\9dе Ð²Ð´Ð°Ð»Ð¾ Ñ\81Ñ\8f Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ати файл імпорту. Розмір файлу перевышує становлену меджу.',
+'importuploaderrorpartial' => 'Ð\9dе Ð²Ð´Ð°Ð»Ð¾ Ñ\81Ñ\8f Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ñ\82и Ñ\96мпоÑ\80Ñ\82нÑ\8bй Ñ\84айл. Ð¤Ð°Ð¹Ð» Ð±Ñ\8bв Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ð½Ñ\8bй Ð»ÐµÐ¼ Ñ\87аÑ\81Ñ\82ково.',
+'importuploaderrortemp' => 'Ð\9dе Ð²Ð´Ð°Ð»Ð¾ Ñ\81Ñ\8f Ð·Ð°Ð»Ð°Ð´Ð¾Ð²ати імпортный файл. Не є к діспозіції дочасный адресарь.',
+'import-parse-failure' => 'Хыба під час імпорту XML',
 'import-noarticle' => 'Не є сторінка про імпорт!',
 'import-nonewrevisions' => 'Вшыткы верзії уж были скоре імпортованы',
 'xml-error-string' => '$1 на рядку $2, стовпець $3 (байт $4): $5',
-'import-upload' => 'Ð\9dагÑ\80ати XML дата',
+'import-upload' => 'Ð\97аладовати XML дата',
 'import-token-mismatch' => 'Стратили ся дата релації. Спробуйте то знову.',
 'import-invalid-interwiki' => 'Із зазначеной вікі ся не дасть імпортовати.',
 'import-error-edit' => 'Сторінка „$1“ ся не наімпортовала, бо не мате право єй едітовати.',
@@ -2580,7 +2631,7 @@ $1',
 # Import log
 'importlogpage' => 'Книга імпортів',
 'importlogpagetext' => 'На тій сторінцї ся зображують адміністраторскы імпорты сторінок враховано едітовань з іншых вікі.',
-'import-logentry-upload' => 'Ñ\96мпоÑ\80Ñ\82овав [[$1]] Ð½Ð°Ñ\87Ñ\96Ñ\82анём файлу',
+'import-logentry-upload' => 'Ñ\96мпоÑ\80Ñ\82овав [[$1]] Ð·Ð°Ð»Ð°Ð´Ð¾Ð²анём файлу',
 'import-logentry-upload-detail' => '$1 {{PLURAL:$1|ревізія|ревізії|ревізій}}',
 'import-logentry-interwiki' => 'міджівікі імпорт $1',
 'import-logentry-interwiki-detail' => '$1 {{PLURAL:$1|ревізія|ревізії|ревізій}} з $2',
@@ -2592,6 +2643,9 @@ $1',
 'javascripttest-pagetext-noframework' => 'Тота сторінка є резервована про тестованя JavaScript.',
 'javascripttest-pagetext-unknownframework' => 'Незнаный фреймворк тестованя „$1“.',
 'javascripttest-pagetext-frameworks' => 'Просиме, звольте єден з наступных фреймворків тестованя : $1',
+'javascripttest-pagetext-skins' => 'Звольте взгляд під котрым ся мають тесты спустити:',
+'javascripttest-qunit-intro' => 'Смотьте [$1 документацію тестованя] на mediawiki.org',
+'javascripttest-qunit-heading' => 'Сет тестів JavaScript в MediaWiki QUnit',
 
 # Tooltip help for the actions
 'tooltip-pt-userpage' => 'Ваша сторінка хоснователя',
@@ -2608,9 +2662,9 @@ $1',
 'tooltip-ca-edit' => 'Тоту сторінку можете едітовати. Просиме, хоснуйте перегляд перед уложінём.',
 'tooltip-ca-addsection' => 'Створити нову секцію',
 'tooltip-ca-viewsource' => 'Тота сторінка є замкнута.
-Можете відїти єй код.',
+Можете видїти єй код.',
 'tooltip-ca-history' => 'Минулы верзії той сторінкы',
-'tooltip-ca-protect' => 'Ð¥Ñ\80анÑ\8c тоту сторінку',
+'tooltip-ca-protect' => 'Ð\92Ñ\81окоÑ\82иÑ\82и тоту сторінку',
 'tooltip-ca-unprotect' => 'Змінити замок той сторінкы',
 'tooltip-ca-delete' => 'Вымазати тоту сторінку',
 'tooltip-ca-undelete' => 'Обновити едітованя той сторінкы выконаны перед єй змазанём',
@@ -2618,46 +2672,48 @@ $1',
 'tooltip-ca-watch' => 'Придати гевсю сторінку до вашого списку слїдованых сторінок',
 'tooltip-ca-unwatch' => 'Одобрати тоту сторінку з вашого списку слїдованых сторінок',
 'tooltip-search' => 'Глядати {{SITENAME}}',
-'tooltip-search-go' => 'Ð\9fеÑ\80ейÑ\82и Ð´Ð¾ Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b, Ñ\88Ñ\82о Ð¼Ð°Ñ\94 Ñ\82оÑ\87но таку назву (кідь екзістує)',
-'tooltip-search-fulltext' => 'Ð\9dайÑ\82и Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ð¿Ñ\80о Ñ\82оÑ\82 Ñ\82екÑ\81Ñ\82',
+'tooltip-search-go' => 'Ð\9fеÑ\80ейÑ\82и Ð½Ð° Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\83, Ñ\88Ñ\82о Ð¼Ð°Ñ\82Ñ\8c Ð°ÐºÑ\83Ñ\80аÑ\82 таку назву (кідь екзістує)',
+'tooltip-search-fulltext' => 'Ð\9dайÑ\82и Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ð· Ñ\82Ñ\8bм Ñ\82екÑ\81Ñ\82ом',
 'tooltip-p-logo' => 'Головна сторінка',
 'tooltip-n-mainpage' => 'Перейти на Головну сторінку',
 'tooltip-n-mainpage-description' => 'Перейти на головну сторінку',
 'tooltip-n-portal' => 'О проєктї, што можете зробити, де ся што находить',
 'tooltip-n-currentevents' => 'Актуалны подїї',
 'tooltip-n-recentchanges' => 'Список послїднїх змін',
-'tooltip-n-randompage' => 'Ð\97обÑ\80ажÑ\96нÑ\8f Ð½Ð°Ð³Ð¾Ð´Ð½Ð¾Ð¹ Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b',
+'tooltip-n-randompage' => 'Ð\86Ñ\82и Ð½Ð° Ñ\82Ñ\80аÑ\84Ñ\83нковÑ\83 Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\83',
 'tooltip-n-help' => 'Посмотрити поміч',
 'tooltip-t-whatlinkshere' => 'Список вшыткых сторінок, што одказують ся на тоту сторінку',
 'tooltip-t-recentchangeslinked' => 'Послїднї зміны на сторінках, котры мають одказ на тїй сторінцї',
 'tooltip-feed-rss' => 'RSS канал про тоту сторінку',
-'tooltip-feed-atom' => 'Atom ÐºÐ°Ð½Ð°Ð» Ð¿Ñ\80о Ð³ÐµÐ²Ñ\81Ñ\8e Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\83',
+'tooltip-feed-atom' => 'Atom ÐºÐ°Ð½Ð°Ð» Ð³ÐµÐ²Ñ\81Ñ\91й Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b',
 'tooltip-t-contributions' => 'Перегляд приспевків того хоснователя',
-'tooltip-t-emailuser' => 'Послати е-маіл тому хоснователёви',
-'tooltip-t-upload' => 'Ð\9dагÑ\80ати файлы',
+'tooltip-t-emailuser' => 'Послати імейл тому хоснователёви',
+'tooltip-t-upload' => 'Ð\97аладовати файлы',
 'tooltip-t-specialpages' => 'Список вшыткых шпеціалных сторінок',
-'tooltip-t-print' => 'Ð\92еÑ\80зÑ\96Ñ\8f Ñ\82ой Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ð¿Ñ\80о Ð´Ñ\80Ñ\83к',
+'tooltip-t-print' => 'Ð\92еÑ\80зÑ\96Ñ\8f Ñ\82ой Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\8b Ð´Ð¾ Ð´Ñ\80Ñ\83кÑ\83',
 'tooltip-t-permalink' => 'Перманентный одказ на тоту верзію сторінкы',
 'tooltip-ca-nstab-main' => 'Обсяг сторінкы',
-'tooltip-ca-nstab-user' => 'Відїти сторінку хоснователя',
+'tooltip-ca-nstab-user' => 'Видїти сторінку хоснователя',
 'tooltip-ca-nstab-media' => 'Вказати сторінку файлу',
-'tooltip-ca-nstab-special' => 'Шпеціална сторінка, она є недоступна про едітованя',
+'tooltip-ca-nstab-special' => 'Шпеціална сторінка, тай єй не годен едітовати.',
 'tooltip-ca-nstab-project' => 'Сторінка проєкту',
 'tooltip-ca-nstab-image' => 'Видїти код сторінкы',
 'tooltip-ca-nstab-mediawiki' => 'Вказати повідомлїня сістемы',
-'tooltip-ca-nstab-template' => 'Відїти шаблону',
+'tooltip-ca-nstab-template' => 'Видїти шаблону',
 'tooltip-ca-nstab-help' => 'Видїти сторінку помочі',
 'tooltip-ca-nstab-category' => 'Сторінка катеґорії',
 'tooltip-minoredit' => 'Позначіти тото як незначне едітованя',
 'tooltip-save' => 'Уложыти вашы зміны',
 'tooltip-preview' => 'Нагляд сторінкы, просиме Вас, хоснуйте перед уложінём!',
 'tooltip-diff' => 'Вказати зміны, што были зроблены в тексті.',
-'tooltip-compareselectedversions' => 'Відїти роздїл міджі двома указаныма верзіями той сторінкы.',
+'tooltip-compareselectedversions' => 'Видїти роздїл міджі двома указаныма верзіями той сторінкы.',
 'tooltip-watch' => 'Придати тоту сторінку до списку слїдованых',
+'tooltip-watchlistedit-normal-submit' => 'Остранити надписы',
+'tooltip-watchlistedit-raw-submit' => 'Актуалізовати список слїдованых сторінок',
 'tooltip-recreate' => 'Обновити сторінку і кідь была змазана',
 'tooltip-upload' => 'Почати одосыланя',
 'tooltip-rollback' => 'Єдным кликом вернути зміны, зроблены послїдным приспівателём',
-'tooltip-undo' => 'Зрушыти зміны і вказати попереднїй перегляд. Дозволює придати причіну до ресуме.',
+'tooltip-undo' => 'Зрушыти зміны і вказати попереднїй перегляд. Дозволює придати прічіну до ресуме.',
 'tooltip-preferences-save' => 'Уложыти наставлїня',
 'tooltip-summary' => 'Задайте курте згорнутя',
 
@@ -2683,6 +2739,7 @@ $1',
 'spambot_username' => 'MediaWiki очістка спаму',
 'spam_reverting' => 'Реверт на послїдню верзію необсягуючу одказы на $1',
 'spam_blanking' => 'Вшыткы ревізії обсяговали одказы на $1, выпорожнєны',
+'spam_deleting' => 'Вшыткы ревізії обсяговали одказы на $1, змазане',
 
 # Info page
 'pageinfo-title' => 'Інформація про "$1"',
@@ -2706,7 +2763,7 @@ $1',
 'rcpatroldisabledtext' => 'Патролованя послїднїх змін є моментално выпнута.',
 'markedaspatrollederror' => 'Не дасть ся означіти як перевірене',
 'markedaspatrollederrortext' => 'Мусите зволити ревізію, котра має быти означена як перевірена.',
-'markedaspatrollederror-noautopatrol' => 'Не маєте доволене означовати властны едітованя як перевірены.',
+'markedaspatrollederror-noautopatrol' => 'Ð\9dе Ð¼Ð°Ñ\94Ñ\82е Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ðµ Ð¾Ð·Ð½Ð°Ñ\87оваÑ\82и Ð²Ð»Ð°Ñ\81Ñ\82нÑ\8b ÐµÐ´Ñ\96Ñ\82ованÑ\8f Ñ\8fк Ð¿ÐµÑ\80евÑ\96Ñ\80енÑ\8b.',
 
 # Patrol log
 'patrol-log-page' => 'Книга перевіреных едітовань',
@@ -2715,8 +2772,8 @@ $1',
 
 # Image deletion
 'deletedrevision' => 'Змазана стара ревізія $1',
-'filedeleteerror-short' => 'Ð¥Ñ\8bба Ð¿Ð¾Ñ\87аÑ\81 Ð¼Ð°Ð·Ð°Ð½Ñ\8f Ñ\84айлÑ\83: $1',
-'filedeleteerror-long' => 'Взникла хыба почас мазаня файлу:
+'filedeleteerror-short' => 'Хыба мазаня файлу: $1',
+'filedeleteerror-long' => 'Выникла хыба під час мазаня файлу:
 
 $1',
 'filedelete-missing' => 'Файл „$1“ ся не дасть змазати, бо не екзістує.',
@@ -2753,13 +2810,13 @@ $1',
 # Special:NewFiles
 'newimages' => 'Ґалерія новых файлів',
 'imagelisttext' => "Ниже є {{plural:$1|єден файл|список '''$1'''&nbsp;файлів сортованых $2|список '''$1'''&nbsp;файлів сортованых $2}}.",
-'newimages-summary' => 'Ð\9dа Ñ\82Ñ\96й Ñ\88пеÑ\86Ñ\96алнÑ\96й Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\97 Ñ\81Ñ\8f Ð·Ð¾Ð±Ñ\80ажÑ\83Ñ\8eÑ\82Ñ\8c Ð¾Ñ\81Ñ\82аÑ\82нÑ\97 Ð½Ð°Ñ\87Ñ\96Ñ\82аны файлы.',
+'newimages-summary' => 'Ð\9dа Ñ\82Ñ\96й Ñ\88пеÑ\86Ñ\96алнÑ\96й Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\97 Ñ\81Ñ\8f Ð·Ð¾Ð±Ñ\80ажÑ\83Ñ\8eÑ\82Ñ\8c Ð¾Ñ\81Ñ\82аÑ\82нÑ\97 Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аны файлы.',
 'newimages-legend' => 'Філтер',
 'newimages-label' => 'Назва файлу (або єй часть):',
 'showhidebots' => '($1 ботів)',
 'noimages' => 'Не є што зобразити.',
 'ilsubmit' => 'Глядати',
-'bydate' => 'подлÑ\8f Ð´Ð°Ñ\82Ñ\83мÑ\83',
+'bydate' => 'за Ð´Ð°Ñ\82Ñ\83мом',
 'sp-newimages-showfrom' => 'Вказати новы файлы почінаючі од $2, $1',
 
 # Video information, used by Language::formatTimePeriod() to format lengths in the above messages
@@ -2783,7 +2840,7 @@ $1',
 Кідь ся файл зедітовав по вытворїню, даякы параметры можуть не одповідати тому образку.',
 'metadata-expand' => 'Вказати додатковы детайлы',
 'metadata-collapse' => 'Сховати додатковы детайлы',
-'metadata-fields' => 'Ð\9fоложкÑ\8b Ð¼ÐµÑ\82адаÑ\82 Ð¾Ð±Ñ\80азÑ\87Ñ\96кÑ\96в Ð·Ð°Ð·Ð½Ð°Ñ\87енÑ\8b Ñ\83 Ñ\82Ñ\96м Ð¿Ð¾Ð²Ñ\96домлÑ\97нÑ\8e Ð±Ñ\83дÑ\83Ñ\82Ñ\8c Ð½Ð° Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\97 Ð· Ð¿Ð¾Ð¿Ð¸Ñ\81ом Ð²Ñ\8bпиÑ\81анÑ\8b Ð²Ñ\81е. Ð\9fÑ\80о Ð·Ð¾Ð±Ñ\80ажÑ\96нÑ\8f Ð´Ñ\80Ñ\83гÑ\8bÑ\85 буде треба кликнути на  „зобразити детайлы“.
+'metadata-fields' => 'Ð\9fоложкÑ\8b Ð¼ÐµÑ\82адаÑ\82 Ð¾Ð±Ñ\80азÑ\87Ñ\96кÑ\96в Ð·Ð°Ð·Ð½Ð°Ñ\87енÑ\8b Ñ\83 Ñ\82Ñ\96м Ð¿Ð¾Ð²Ñ\96домлÑ\97нÑ\8e Ð±Ñ\83дÑ\83Ñ\82Ñ\8c Ð½Ð° Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\97 Ð· Ð¿Ð¾Ð¿Ð¸Ñ\81ом Ð²Ñ\8bпиÑ\81анÑ\8b Ð²Ñ\81е. Ð\96ебÑ\8b Ð²ÐºÐ°Ð·Ð°Ñ\82и Ð´Ñ\80Ñ\83гÑ\8b, буде треба кликнути на  „зобразити детайлы“.
 * make
 * model
 * datetimeoriginal
@@ -2824,7 +2881,7 @@ $1',
 'exif-imagedescription' => 'Назва образку',
 'exif-make' => 'Выробник фотоапарату',
 'exif-model' => 'Модел фотоапарату',
-'exif-software' => 'Проґрамове забезпечіня',
+'exif-software' => 'Проґрамове забеспечіня',
 'exif-artist' => 'Автор',
 'exif-copyright' => 'Властник авторьскых прав',
 'exif-exifversion' => 'Верзія Exif',
@@ -2887,7 +2944,7 @@ $1',
 'exif-gpslongitude' => 'Ґеоґрафічна довжка',
 'exif-gpsaltituderef' => 'Над/підморьска вышка/глубка',
 'exif-gpsaltitude' => 'Надморьска вышка',
-'exif-gpstimestamp' => 'GPS Ñ\87аÑ\81 (подлÑ\8f Ð°Ñ\82омовÑ\8bÑ\85 Ð³Ð¾Ð´Ð¸Ð½)',
+'exif-gpstimestamp' => 'GPS Ñ\87аÑ\81 (аÑ\82омовÑ\8b Ð³Ð¾Ð´Ð¸Ð½Ñ\8b)',
 'exif-gpssatellites' => 'Сателіты хоснованы про міряня',
 'exif-gpsstatus' => 'Статус приїмача',
 'exif-gpsmeasuremode' => 'Режім міряня',
@@ -2953,7 +3010,7 @@ $1',
 'exif-datetimemetadata' => 'Датум послїднёй управы метадат',
 'exif-nickname' => 'Неформална назва образчіка',
 'exif-rating' => 'Рейтінґ (1–5)',
-'exif-rightscertificate' => 'ЦеÑ\80Ñ\82Ñ\96Ñ\84Ñ\96каÑ\82 Ñ\80Ñ\8fджÑ\96ня прав',
+'exif-rightscertificate' => 'ЦеÑ\80Ñ\82Ñ\96Ñ\84Ñ\96каÑ\82 Ñ\81пÑ\80авованя прав',
 'exif-copyrighted' => 'Статус авторьскых прав',
 'exif-copyrightowner' => 'Властник авторьскых прав',
 'exif-usageterms' => 'Условія хоснованя',
@@ -2964,7 +3021,7 @@ $1',
 'exif-attributionurl' => 'Кідь хоснуєте тото дїло, зазначте одказ',
 'exif-preferredattributionname' => 'Кідь хоснуєте тото дїло, зазначте автора',
 'exif-pngfilecomment' => 'Позначкы ку файлу PNG',
-'exif-disclaimer' => 'Ð\92Ñ\8bлÑ\83Ñ\87Ñ\96нÑ\8f Ð·Ð¾Ð´Ð¿Ð¾Ð²Ñ\96дноÑ\81Ñ\82и',
+'exif-disclaimer' => 'Вылучіня одповідности',
 'exif-contentwarning' => 'Упозорнїня ку обсягу',
 'exif-giffilecomment' => 'Позначкы ку файлу GIF',
 'exif-intellectualgenre' => 'Тіп положкы',
@@ -2982,7 +3039,7 @@ $1',
 'exif-compression-3' => 'Кодованя факсів CCITT Group 3',
 'exif-compression-4' => 'Кодованя факсів CCITT Group 4',
 
-'exif-copyrighted-true' => 'Ð¥Ñ\80анене авторьскым правом',
+'exif-copyrighted-true' => 'СокоÑ\87ене авторьскым правом',
 'exif-copyrighted-false' => 'Вольне дїло',
 
 'exif-unknowndate' => 'Незнамый датум',
@@ -3198,72 +3255,72 @@ $1',
 'limitall' => 'вшыткы',
 
 # E-mail address confirmation
-'confirmemail' => 'Підтверджіня адресы ел. пошты',
+'confirmemail' => 'Потверджіня адресы ел. пошты',
 'confirmemail_noemail' => 'Во своїм [[Special:Preferences|хосновательскім наставлїню]] сьте не зазначіли платну адресу ел. пошты.',
-'confirmemail_text' => 'Тота вікі выжадує, жебы сьте перед хоснованым дакотрых функцій підтвердили свою адресу електронічной пошты. Кликнутём на клапку ниже одошлете підтверджовачій лист на вами зазначену адресу. Тот лист обсягує одказ і код підтверджіня; зображінём одказованой сторінкы во своїм інтернетовім переглядачу підтвердите, же зазначена адреса є платна.',
-'confirmemail_pending' => 'Підтверджовачій код быв посланый ва вашу адресу ел. пошты.
+'confirmemail_text' => 'Тота вікі выжадує, жебы сьте перед хоснованым дакотрых функцій потвердили свою адресу електронічной пошты. Кликнутём на клапку ниже одошлете потверджовачій лист на вами зазначену адресу. Тот лист обсягує одказ і код потверджіня; зображінём одказованой сторінкы во своїм інтернетовім переглядачу потвердите, же зазначена адреса є платна.',
+'confirmemail_pending' => 'Потверджовачій код быв посланый ва вашу адресу ел. пошты.
 Кідь сьте собі конто створили перед моментом, спробуйте на доручіня коду пару минут почекати, покы пожадате о новый.',
-'confirmemail_send' => 'Одослати підтверджовачій код',
-'confirmemail_sent' => 'Підтверджовачій лист быв посланый.',
-'confirmemail_oncreate' => 'На вашу адресу ел. пошты быв посланый підтверджовачій код.
+'confirmemail_send' => 'Одослати потверджовачій код',
+'confirmemail_sent' => 'Потверджовачій лист быв посланый.',
+'confirmemail_oncreate' => 'На вашу адресу ел. пошты быв посланый потверджовачій код.
 Тот код не треба про приголошіня, але буде го треба про актівацію дакотрых функцій заложеных на хоснованю ел. пошты.',
-'confirmemail_sendfailed' => '{{GRAMMAR:3sg|{{SITENAME}}}} Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило Ð¾Ð´Ð¾Ñ\81лаÑ\82и Ð¿Ñ\96дÑ\82веÑ\80джоваÑ\87Ñ\96й Ð»Ð¸Ñ\81Ñ\82. Ð\9fеÑ\80евÑ\96Ñ\80те ці адреса не обсягує хыбны буквы.
+'confirmemail_sendfailed' => '{{GRAMMAR:3sg|{{SITENAME}}}} Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°Ð»Ð¾ Ð¾Ð´Ð¾Ñ\81лаÑ\82и Ð¿Ð¾Ñ\82веÑ\80джоваÑ\87Ñ\96й Ð»Ð¸Ñ\81Ñ\82. Ð\9fеÑ\80евÑ\96Ñ\80Ñ\8cте ці адреса не обсягує хыбны буквы.
 
 Поштовый проґрам одповив: $1',
-'confirmemail_invalid' => 'Неплатный підтверджовачій код. Може уж уплинула платность коду.',
-'confirmemail_needlogin' => 'Про підтверджіня своёй адрес ел. пошты ся мусите $1.',
-'confirmemail_success' => 'Ваша адреса ел. пошты была підтверджена. Нынї ся можете [[Special:UserLogin|приголосити]] і хосновати вікі.',
-'confirmemail_loggedin' => 'Ваша адреса ел. пошты была підтверджена.',
-'confirmemail_error' => 'Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило Ñ\81Ñ\8f Ñ\83ложÑ\8bÑ\82и Ð²Ð°Ñ\88е Ð¿Ñ\96дтверджіня.',
-'confirmemail_subject' => 'Підтверджіня адресы ел. пошты про  {{grammar:4sg|{{SITENAME}}}}',
+'confirmemail_invalid' => 'Неправильный потверджовачій код. Може уж уплынула платность коду.',
+'confirmemail_needlogin' => 'Про потверджіня своёй адрес ел. пошты ся мусите $1.',
+'confirmemail_success' => 'Ваша адреса ел. пошты была потверджена. Нынї ся можете [[Special:UserLogin|приголосити]] і хосновати вікі.',
+'confirmemail_loggedin' => 'Ваша адреса ел. пошты была потверджена.',
+'confirmemail_error' => 'Ð\9dе Ð²Ð´Ð°Ð»Ð¾ Ñ\81Ñ\8f Ñ\83ложÑ\8bÑ\82и Ð²Ð°Ñ\88е Ð¿Ð¾тверджіня.',
+'confirmemail_subject' => 'Потверджіня адресы ел. пошты про  {{grammar:4sg|{{SITENAME}}}}',
 'confirmemail_body' => 'Хтось (асі вы, з IP адресы $1) собі реґістровав конто з меном "$2" і тов адресов ел. пошты на {{grammar:6sg|{{SITENAME}}}}.
 
-Кідь собі желате актівовати функції ел. пошты на {{grammar:6sg|{{SITENAME}}}}, так про підтверджіня, же тота адреса справды належыть вам, перейдите своїм інтернетовым переглядачом на наслїдуючу адресу:
+Кідь собі желате актівовати функції ел. пошты на {{grammar:6sg|{{SITENAME}}}}, так про потверджіня, же тота адреса справды належыть вам, перейдите своїм інтернетовым переглядачом на слїдуючу адресу:
 
 $3
 
-Кідь сьте о тото підтверджіня  *не жадали*, кликните на наслїднїй одказ, котрым підтверджіня зрушыте:
+Кідь сьте о тото потверджіня  *не жадали*, кликните на слїдуючій одказ, котрым потверджіня зрушыте:
 
 $5
 
-Платность того коду підтверджіня експірує $4.',
+Платность того коду потверджіня експірує $4.',
 'confirmemail_body_changed' => 'Хтось (асі вы, з IP адресы $1),
 змінив адресу ел. пошты ку конту "$2" на {{grammar:6sg|{{SITENAME}}}} на тоту адресу.
 
-Кідь собі желате актівовати функції ел. пошты на {{grammar:6sg|{{SITENAME}}}}, так про підтверджіня, же тота адреса справды належыть вам, перейдите своїм інтернетовым переглядачом на наслїдуючу адресу:
+Кідь собі желате актівовати функції ел. пошты на {{grammar:6sg|{{SITENAME}}}}, так про потверджіня, же тота адреса справды належыть вам, перейдьте своїм інтернетовым переглядачом на наслїдуючу адресу:
 
 $3
 
-Кідь сьте о тото підтверджіня  *не жадали*, кликните на наслїднїй одказ, котрым підтверджіня зрушыте:
+Кідь сьте о тото потверджіня  *не жадали*, кликните на слїдуючій одказ, котрым потверджіня зрушыте:
 
 $5
 
-Платность того коду підтверджіня експірує $4.',
+Платность того коду потверджіня експірує $4.',
 'confirmemail_body_set' => 'Дахто (асі вы, з IP адресы $1) наставив імейлову адресу 
 конта „$2“ на {{grammar:6sg|{{SITENAME}}}} на тоту адресу.
 
 Кідь хочете знову актівовати імейловы функції на
-{{grammar:6sg|{{SITENAME}}}}, так про підтверджіня, же тота адреса справды
+{{grammar:6sg|{{SITENAME}}}}, та жебы потвердити, же тота адреса справды
 належыть вам, ідьте своїм інтернетовым перезерачом на адресу ниже:
 
 $3
 
 Кідь вам тото конто *не належыть*, кликните на наступный
-одказ з чім підтверджіня зрушыте:
+одказ з чім потверджіня зрушыте:
 
 $5
 
 Платность того коду кінчіть $4.',
-'confirmemail_invalidated' => 'Підтверджіня адресы електронічной пошты было зрушене',
-'invalidateemail' => 'Зрушыти підтверджіня адресы електронічной пошты',
+'confirmemail_invalidated' => 'Потверджіня адресы електронічной пошты было зрушене',
+'invalidateemail' => 'Зрушыти потверджіня адресы електронічной пошты',
 
 # Scary transclusion
 'scarytranscludedisabled' => '[Вкладаня шаблон міджі вікі є выпнуте]',
-'scarytranscludefailed' => '[Ð\9dе Ð¿Ð¾Ð´Ð°Ñ\80ило Ñ\81Ñ\8f Ð½Ð°Ñ\87Ñ\96Ñ\82ати шаблону про $1]',
+'scarytranscludefailed' => '[Ð\9dе Ð²Ð´Ð°Ð»Ð¾ Ñ\81Ñ\8f Ð½Ð°Ñ\82Ñ\8fгнÑ\83ти шаблону про $1]',
 'scarytranscludetoolong' => '[URL дуже довгый]',
 
 # Delete conflict
-'deletedwhileediting' => "'''Увага:''' Ð¿Ð¾Ñ\87аÑ\81 вашой едітації была тота сторінка змазана!",
+'deletedwhileediting' => "'''Увага:''' Ð±Ñ\96гом вашой едітації была тота сторінка змазана!",
 'confirmrecreate' => 'Хоснователь [[User:$1|$1]] ([[User talk:$1|діскузія]]) тоту сторінку змазав потім, як сьте зачали едітовати з причінов:
 : „$2“
 Справды собі хочете знову тоту сторінку створити?',
@@ -3307,8 +3364,8 @@ $5
 'autosumm-new' => 'Створена сторінка: $1',
 
 # Live preview
-'livepreview-loading' => 'Ð\9dагÑ\80аваня...',
-'livepreview-ready' => 'Ð\9dагÑ\80аваня… Готово!',
+'livepreview-loading' => 'Ð\97аладовованя...',
+'livepreview-ready' => 'Ð\97аладовованя… Готово!',
 'livepreview-failed' => 'Швыдкый нагляд не є доступный! Спробуйте хосновати звычайный нагляд.',
 'livepreview-error' => 'Не успішне споїня: $1 "$2". Хоснуйте звычайный нагляд.',
 
@@ -3369,7 +3426,7 @@ $5
 'version-license' => 'Ліценція',
 'version-poweredby-credits' => "Тота вікі біжыть на '''[//www.mediawiki.org/ MediaWiki]''', copyright © 2001–$1 $2.",
 'version-poweredby-others' => 'іншы',
-'version-license-info' => 'MediaWiki Ñ\94 Ñ\81лободнÑ\8bй Ñ\81оÑ\84Ñ\82веÑ\80; Ð¼Ð¾Ð¶ÐµÑ\82е Ð³Ð¾ Ñ\88Ñ\8bÑ\80иÑ\82и Ð°Ð±Ð¾ Ñ\83пÑ\80авлÑ\8fÑ\82и Ð¿Ð¾Ð´Ð»Ñ\8f Ñ\83Ñ\81ловÑ\96й GNU General Public License, Ð²Ñ\8bдаваной Free Software Foundation; Ð±Ñ\83дÑ\8c Ð²ÐµÑ\80зÑ\96Ñ\8f 2 Ñ\82ой Ð»Ñ\96Ñ\86енÑ\86Ñ\96Ñ\97 Ð°Ð±Ð¾ (подлÑ\8f Ð²Ð°Ñ\88ого Ñ\83важÑ\96нÑ\8f) будьяка пізнїша верзія.
+'version-license-info' => 'MediaWiki Ñ\94 Ñ\81лободнÑ\8bй Ñ\81оÑ\84Ñ\82веÑ\80; Ð¼Ð¾Ð¶ÐµÑ\82е Ð³Ð¾ Ñ\88Ñ\8bÑ\80иÑ\82и Ð°Ð±Ð¾ Ñ\83пÑ\80авлÑ\8fÑ\82и Ð² Ð·Ð³Ð¾Ð´Ñ\97 Ð· Ñ\83Ñ\81ловÑ\96Ñ\8fми GNU General Public License, Ð²Ñ\8bдаваной Free Software Foundation; Ð±Ñ\83дÑ\8c Ð²ÐµÑ\80зÑ\96Ñ\8f 2 Ñ\82ой Ð»Ñ\96Ñ\86енÑ\86Ñ\96Ñ\97 Ð°Ð±Ð¾ (Ñ\8fк Ñ\83важÑ\8bÑ\82е) будьяка пізнїша верзія.
 
 MediaWiki є дістрібуована в надїї, же буде хосновна, але БЕЗ БУДЬЯКОЙ ЗАРУКЫ; не давають ся ани зарукы ПРОДАЙНОСТИ або ВАЛУШНОСТИ ПРО СТАНОВЛЕНЫЙ ЦІЛЬ. Детайлы ся дочітате в текстї  GNU General Public License.
 
@@ -3377,6 +3434,9 @@ MediaWiki є дістрібуована в надїї, же буде хосно
 'version-software' => 'Іншталованый софтвер',
 'version-software-product' => 'Продукт',
 'version-software-version' => 'Верзія',
+'version-entrypoints' => 'URL вступных пунктів',
+'version-entrypoints-header-entrypoint' => 'Вступный пункт',
+'version-entrypoints-header-url' => 'URL',
 
 # Special:FilePath
 'filepath' => 'Стежка ку файлу',
@@ -3467,8 +3527,8 @@ MediaWiki є дістрібуована в надїї, же буде хосно
 'htmlform-select-badoption' => 'Вами зазначена величіна не є доступна можность.',
 'htmlform-int-invalid' => 'Зазначена величіна не є ціле чісло.',
 'htmlform-float-invalid' => 'Зазначена величіна не є чісло.',
-'htmlform-int-toolow' => 'Ð\92ами Ð·Ð°Ð·Ð½Ð°Ñ\87ена Ð²ÐµÐ»Ð¸Ñ\87Ñ\96на Ñ\94 Ð¼ÐµÐ½Ñ\88а Ñ\8fк Ð´Ð¾волене мінімум $1',
-'htmlform-int-toohigh' => 'Ð\92ами Ð·Ð°Ð·Ð½Ð°Ñ\87ена Ð²ÐµÐ»Ð¸Ñ\87Ñ\96на Ñ\94 Ð¼ÐµÐ½Ñ\88а Ñ\8fк Ð´Ð¾волене максімум $1',
+'htmlform-int-toolow' => 'Ð\92ами Ð·Ð°Ð·Ð½Ð°Ñ\87ене Ð·Ð½Ð°Ñ\87Ñ\96нÑ\8f Ñ\94 Ð¼ÐµÐ½Ñ\88е Ñ\8fк Ð´Ð¾Ð·волене мінімум $1',
+'htmlform-int-toohigh' => 'Ð\92ами Ð·Ð°Ð·Ð½Ð°Ñ\87ене Ð·Ð½Ð°Ñ\87Ñ\96нÑ\8f Ñ\94 Ð¼ÐµÐ½Ñ\88а Ñ\8fк Ð´Ð¾Ð·волене максімум $1',
 'htmlform-required' => 'Тота величіна є повинна',
 'htmlform-submit' => 'Одослати',
 'htmlform-reset' => 'Вернути зміны',
@@ -3481,6 +3541,20 @@ MediaWiki є дістрібуована в надїї, же буде хосно
 # New logging system
 'logentry-delete-delete' => '$1 змазав сторінку $3',
 'logentry-delete-restore' => '$1 обновив сторінку $3',
+'logentry-delete-event' => '$1 змінив відимость {{PLURAL:$5|протоколового запису|$5 протоколовых записів}} к сторінцї $3: $4',
+'logentry-delete-revision' => '$1 змінив відимость {{PLURAL:$5|ревізії|$5 ревізій}} на сторінцї $3: $4',
+'logentry-delete-event-legacy' => '$1 змінив відимость протоколовых записів к сторінцї $3',
+'logentry-delete-revision-legacy' => '$1 змінив відимость ревізій на сторінцї $3',
+'logentry-suppress-delete' => '$1 утаїв сторінку $3',
+'logentry-suppress-event' => '$1 тайком змінив відимость {{PLURAL:$5|протоколового запису|$5 протоколовых записів}} к сторінцї $3: $4',
+'logentry-suppress-revision' => '$1 тайком змінив відимость {{PLURAL:$5|ревізії|$5 ревізій}} на сторінцї $3: $4',
+'logentry-suppress-event-legacy' => '$1 тайком змінив відимость протоколовых записів к сторінцї $3',
+'logentry-suppress-revision-legacy' => '$1 тайком змінив відимость ревізій на сторінцї $3',
+'revdelete-content-hid' => 'скрыти обсяг',
+'revdelete-summary-hid' => 'опис едітованя схованый',
+'revdelete-uname-hid' => 'імя хоснователя сховане',
+'revdelete-content-unhid' => 'обсяг одкрытый',
+'revdelete-summary-unhid' => 'опис едітованя одкрытый',
 'revdelete-uname-unhid' => 'імя хоснователя одкрыте',
 'revdelete-restricted' => 'приданы обмеджіня про адміністраторів',
 'revdelete-unrestricted' => 'зняты обмеджіня про адміністраторів',
@@ -3488,52 +3562,67 @@ MediaWiki є дістрібуована в надїї, же буде хосно
 'logentry-move-move-noredirect' => '$1 переменовав сторінку $3 на $4 без створїня напрямлїня',
 'logentry-move-move_redir' => '$1 переменовав сторінку $3 на $4 з вычерянём напрямлїнём',
 'logentry-move-move_redir-noredirect' => '$1 переменовав сторінку $3 на $4 місце напрямлїня без створїня напрямлїня',
+'logentry-patrol-patrol' => '$1 означів ревізію $4 сторінкы $3 як перевірену',
+'logentry-patrol-patrol-auto' => '$1 автоматічно означів ревізію $4 сторінкы $3 як перевірену',
+'logentry-newusers-newusers' => '$1 створив конто хоснователя',
+'logentry-newusers-create' => '$1 створив конто хоснователя',
+'logentry-newusers-create2' => '$1 створив конто хоснователя $3',
+'logentry-newusers-autocreate' => 'Автоматічно было створене конто $1',
 'newuserlog-byemail' => 'гело послане електронічнов поштов',
 
 # Feedback
+'feedback-bugornote' => 'Кідь сьте прирыхтованый подробно описати технічный проблем, можете [$1 наголосити хыбу].
+Інакше можете схносновати простый формулать ниже. Ваш коментарь буде приданый на сторінку „[$3 $2]“ разом з вашым іменом хоснователя тай інформаціов о тім, якый бровсер хоснуєте.',
 'feedback-subject' => 'Предмет:',
 'feedback-message' => 'Повідомлїня:',
 'feedback-cancel' => 'Сторно',
 'feedback-submit' => 'Одослати одозву',
 'feedback-adding' => 'Коментарь ся придавать на сторінку…',
 'feedback-error1' => 'Хыба: Нерозознаый резултат з API',
-'feedback-error2' => 'Ð¥Ñ\8bба: Ð\95дÑ\96Ñ\82ованÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило',
+'feedback-error2' => 'Ð¥Ñ\8bба: Ð\95дÑ\96Ñ\82ованÑ\8f Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°ло',
 'feedback-error3' => 'Хыба: API не вернуло жадну одповідь',
+'feedback-thanks' => 'Дякуєме! Ваш коментарь быв приданый на сторінку „[$2 $1]“.',
 'feedback-close' => 'Готово',
+'feedback-bugcheck' => 'Герешнї! Лем перевірьте, ці то не єдна з [$1 уж знамых хыб].',
+'feedback-bugnew' => 'Перевірив(а) єм то. Хочу повідомити нову хыбу.',
 
 # API errors
-'api-error-badaccess-groups' => 'Ð\9dе Ð¼Ð°Ñ\82е Ð´Ð¾Ð²Ð¾Ð»ÐµÐ½Ð¾ Ð½Ð°Ð³Ñ\80авати файлы на тоту вікі.',
+'api-error-badaccess-groups' => 'Ð\9dе Ð¼Ð°Ñ\82е Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ð¾ Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾вати файлы на тоту вікі.',
 'api-error-badtoken' => 'Внутрїшня хыба: планый знак.',
-'api-error-copyuploaddisabled' => 'Ð\9dаÑ\87Ñ\96Ñ\82аваня з URL є на тім сервері заказане.',
+'api-error-copyuploaddisabled' => 'Ð\97аладовованя з URL є на тім сервері заказане.',
 'api-error-duplicate' => 'На тій вікі уж {{PLURAL:$1|екзістує [$2 другый файл]|екзістують [$2 іншы файлы]}} з такым самым обсягом.',
 'api-error-duplicate-archive' => '{{PLURAL:$1|быв [$2 другый файл]|были [$2 даякы другы файлы]}} з такым самым обсягом уж гев оперед {{PLURAL:$1|быв|были}}, але {{PLURAL:$1|быв змазаный|были змазаны}}.',
 'api-error-duplicate-archive-popup-title' => '{{PLURAL:$1|дупліцітный файл, якый быв|дупліцітны файл, як были}} змазаны',
 'api-error-duplicate-popup-title' => 'Дупліцітны {{PLURAL:$1|файл|файлы}}',
-'api-error-empty-file' => 'Начітаный файл є порожнїй.',
-'api-error-fetchfileerror' => 'Внутрїшня хыба: дішло ку хыбі почас обтриманя файлу.',
-'api-error-file-too-large' => 'Начітаный файл є барз великый.',
+'api-error-empty-file' => 'Заладованый файл є порожнїй.',
+'api-error-emptypage' => 'Створїня новых, порожнїх сторінк неслободно.',
+'api-error-fetchfileerror' => 'Внутрїшня хыба: трафила ся хыба під час обтриманя файлу.',
+'api-error-fileexists-forbidden' => 'Файл з назвов „$1“ уж екзістує тай не годен го переписати.',
+'api-error-fileexists-shared-forbidden' => 'файл з назвов „$1“ уж екзістує в сполочнім усховищу тай не годен го переписати.',
+'api-error-file-too-large' => 'Заладованый файл є барз великый.',
 'api-error-filename-tooshort' => 'Назва файлу є барз курта.',
 'api-error-filetype-banned' => 'Тот тіп файлу є заказаный.',
 'api-error-filetype-missing' => 'Тот файл не мать росшырїня.',
 'api-error-hookaborted' => 'Пожадована вами зміна была одмітнута дакотрым росшырінём.',
-'api-error-http' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило Ñ\81Ñ\8f Ð¿Ñ\80ипоÑ\97Ñ\82и ÐºÑ\83 серверу.',
-'api-error-illegal-filename' => 'ТоÑ\82а Ð½Ð°Ð·Ð²Ð° Ñ\84айлÑ\83 Ð½Ðµ Ñ\94 Ð¿Ð¾волена.',
-'api-error-internal-error' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ð´Ñ\96Ñ\88ло ÐºÑ\83 Ñ\85Ñ\8bбÑ\96 Ð¿Ð¾Ñ\87аÑ\81 Ñ\81пÑ\80аÑ\86ованÑ\8f Ð²Ð°Ñ\88ого Ð½Ð°Ñ\87Ñ\96Ñ\82аного файлу.',
+'api-error-http' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ð½Ðµ Ð²Ð´Ð°Ð»Ð¾ Ñ\81Ñ\8f Ð¿Ñ\80ипоÑ\97Ñ\82и Ðº серверу.',
+'api-error-illegal-filename' => 'ТоÑ\82а Ð½Ð°Ð·Ð²Ð° Ñ\84айлÑ\83 Ð½Ðµ Ñ\94 Ð´Ð¾Ð·волена.',
+'api-error-internal-error' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ð¿Ñ\80иÑ\82Ñ\80аÑ\84ила Ñ\81Ñ\8f Ñ\85Ñ\8bба Ð¿Ñ\96д Ñ\87аÑ\81 Ñ\81пÑ\80аÑ\86ованÑ\8f Ð²Ð°Ñ\88ого Ð·Ð°Ð»Ð°Ð´Ð¾Ð²аного файлу.',
 'api-error-invalid-file-key' => 'Внутрїшня хыба: файл ся не нашов в дочаснім усховіщі.',
 'api-error-missingparam' => 'Внутрїшня хыба: хыбують параметры пожадавкы.',
 'api-error-missingresult' => 'Внутрїшня хыба: не годен становити, ці копірованя было успішне.',
-'api-error-mustbeloggedin' => 'Ð\9fÑ\80о Ð½Ð°Ñ\87Ñ\96Ñ\82аванÑ\8f Ñ\84айлÑ\96в мусите быти приголошеный.',
+'api-error-mustbeloggedin' => 'Ð\96ебÑ\8b Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð°Ñ\82и Ñ\84айлÑ\8b, мусите быти приголошеный.',
 'api-error-mustbeposted' => 'Внутрїшня хыба: пожадавка мусить быти через HTTP POST.',
-'api-error-noimageinfo' => 'Ð\9dаÑ\87Ñ\96Ñ\82аня было успішне, але сервер не додав о файлї жадны інформації.',
-'api-error-nomodule' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ð½Ðµ Ñ\94 Ð½Ð°Ñ\81Ñ\82авленÑ\8bй Ð¼Ð¾Ð´Ñ\83л Ð½Ð°Ñ\87Ñ\96Ñ\82аваня.',
+'api-error-noimageinfo' => 'Ð\97аладованя было успішне, але сервер не додав о файлї жадны інформації.',
+'api-error-nomodule' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ð½Ðµ Ñ\94 Ð½Ð°Ñ\81Ñ\82авленÑ\8bй Ð¼Ð¾Ð´Ñ\83л Ð·Ð°Ð»Ð°Ð´Ð¾Ð²Ð¾ваня.',
 'api-error-ok-but-empty' => 'Внутрїшня хыба: сервер не одповідать.',
-'api-error-overwrite' => 'Не є доволене переписати екзістуючій файл.',
-'api-error-stashfailed' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ñ\81еÑ\80веÑ\80Ñ\83 Ñ\81Ñ\8f Ð½Ðµ Ð¿Ð¾Ð´Ð°Ñ\80ило уложыти дочасный файл.',
+'api-error-overwrite' => 'Ð\9dе Ñ\94 Ð´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ðµ Ð¿ÐµÑ\80епиÑ\81аÑ\82и ÐµÐºÐ·Ñ\96Ñ\81Ñ\82Ñ\83Ñ\8eÑ\87Ñ\96й Ñ\84айл.',
+'api-error-stashfailed' => 'Ð\92нÑ\83Ñ\82Ñ\80Ñ\97Ñ\88нÑ\8f Ñ\85Ñ\8bба: Ñ\81еÑ\80веÑ\80Ñ\83 Ñ\81Ñ\8f Ð½Ðµ Ð²Ð´Ð°ло уложыти дочасный файл.',
 'api-error-timeout' => 'Сервер не одповідав в очекаванім часї.',
-'api-error-unclassified' => 'Ð\94Ñ\96Ñ\88ло ÐºÑ\83 Ð½ÐµÐ·Ð½Ð°Ð¼Ñ\96й Ñ\85Ñ\8bбÑ\96.',
+'api-error-unclassified' => 'ТÑ\80аÑ\84ила Ñ\81Ñ\8f Ð½ÐµÐ·Ð½Ð°Ð¼Ð° Ñ\85Ñ\8bба.',
 'api-error-unknown-code' => 'Незнама хыба: „$1“',
-'api-error-unknown-error' => 'Внутрїшня хыба: дішло ку хыбі при спробі о начітаня файлу.',
+'api-error-unknown-error' => 'Внутрїшня хыба: трафила ся хыба при спробі о заладованя файлу.',
 'api-error-unknown-warning' => 'Незнаме варованя: $1',
+'api-error-unknownerror' => 'Незнама хыба: „$1“',
 'api-error-uploaddisabled' => 'Начітаваня файлів є на тій вікі выпнуте.',
 'api-error-verification-error' => 'Файл є може пошкодженый, або мать плане росшырїня.',
 
@@ -3546,7 +3635,8 @@ MediaWiki є дістрібуована в надїї, же буде хосно
 'duration-years' => '$1 {{PLURAL:$1|рік|рокы|років}}',
 'duration-decades' => '$1 {{PLURAL:$1|декада|декады|декад}}',
 'duration-centuries' => '$1 {{PLURAL:$1|стороча|стороча|стороч}}',
+'duration-millennia' => '$1 {{PLURAL:$1|тісячроча|тісячроча|тісячроч}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|Ñ\94 Ð½ÐµÐ´Ð¾Ð²Ð¾Ð»ÐµÐ½Ñ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84айлÑ\96в|Ñ\81Ñ\83Ñ\82Ñ\8c Ð½ÐµÐ´Ð¾Ð²Ð¾Ð»ÐµÐ½Ñ\8b Ñ\84оÑ\80маÑ\82Ñ\8b Ñ\84айлÑ\96в}}. {{PLURAL:$3|Ð\94оволенÑ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84алÑ\96в|Ð\94оволены форматы файлів суть}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|Ñ\94 Ð½ÐµÐ´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ñ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84айлÑ\96в|Ñ\81Ñ\83Ñ\82Ñ\8c Ð½ÐµÐ´Ð¾Ð·Ð²Ð¾Ð»ÐµÐ½Ñ\8b Ñ\84оÑ\80маÑ\82Ñ\8b Ñ\84айлÑ\96в}}. {{PLURAL:$3|Ð\94озволенÑ\8bй Ñ\84оÑ\80маÑ\82 Ñ\84алÑ\96в Ñ\94\94озволены форматы файлів суть}} $2.',
 );
index 68fb9b3..db88212 100644 (file)
@@ -543,6 +543,10 @@ $1',
 'youhavenewmessages' => 'भवदर्थम् $1 सन्ति। ($2).',
 'newmessageslink' => 'नूतनाः सन्देशाः',
 'newmessagesdifflink' => 'अन्तिमं परिवर्तनम्',
+'youhavenewmessagesfromusers' => '{{PLURAL:$3|अन्ययोजकः|$3 योजकाः}} ($2) इत्यस्मात्  भवतः $1 अस्ति ।',
+'youhavenewmessagesmanyusers' => 'अनैकेभ्यः योजकेभ्यः ते $1 सन्ति $2 ।',
+'newmessageslinkplural' => '{{PLURAL:$1|नूतनः सन्देशः|नूतनसन्देशाः}}',
+'newmessagesdifflinkplural' => 'सद्यः {{PLURAL:$1|परिवर्तनम्|परिवर्तनानि}}',
 'youhavenewmessagesmulti' => 'भवतः कृते $1 मध्ये नूतनः सन्देशः विद्यते',
 'editsection' => 'सम्पाद्यताम्',
 'editold' => 'सम्पाद्यताम्',
@@ -1049,6 +1053,7 @@ $2
 'expansion-depth-exceeded-warning' => 'विस्तारगाहातिक्रान्तपुटम् ।',
 'parser-unstrip-loop-warning' => 'अपट्टरन्धः दृष्टः ।',
 'parser-unstrip-recursion-limit' => 'अपट्टपुनरवतरणमितिः अतिक्रान्ता ।$1',
+'converter-manual-rule-error' => 'मानवीये भाषापरिवर्तने दोषः दृष्टः ।',
 
 # "Undo" feature
 'undo-success' => 'सम्पादनमिदं विपरीतीकर्तुं शक्यते।
@@ -2334,7 +2339,7 @@ $2 द्वारा सम्पादितां अन्तिमावृ
 'modifiedarticleprotection' => '"[[$1]]" इत्येतदर्थं सुरक्षा-स्तरः परिवर्तित: :',
 'unprotectedarticle' => '"[[$1]]"तः सुरक्षा अपमर्जिता ।',
 'movedarticleprotection' => 'सुरक्षणस्य स्तरः  "[[$2]]" तः परिवर्त्य   "[[$1]]" कृतः अस्ति ।',
-'protect-title' => '[[$1]] इत्येतदर्थं सुरक्षा-स्तरः परिवर्तित: :',
+'protect-title' => '"$1" इत्यस्य सुरक्षास्तरं पश्यतु ।',
 'protect-title-notallowed' => '"$1" इत्यस्य सुरक्षास्तरं पश्यतु ।',
 'prot_1movedto2' => '[[$1]] इत्यस्य नामपरिवर्तनं कृत्वा [[$2]] इति कृतम् ।',
 'protect-badnamespace-title' => 'असुरक्षितं नामस्थानम् ।',
@@ -2367,28 +2372,86 @@ $2 द्वारा सम्पादितां अन्तिमावृ
 'protect-existing-expiry' => 'विद्यमानः समाप्तिसमयः  $3, $2',
 'protect-otherreason' => 'अपरं/अतिरिक्तं कारणम् :',
 'protect-otherreason-op' => 'इतर कारणम्',
+'protect-dropdown' => '*सुरक्षायाः सामान्यकारणानि । 
+** अत्यधिकं नाशकत्वम् ।
+** अत्यधिकं शुष्कसन्देशाः ।
+** अफलदायि सम्पादनयुद्धम्
+** अधिकसञ्चारयुक्तपुटानि ।',
+'protect-edit-reasonlist' => 'सुरक्षाकारणानि सम्पादयतु ।',
+'protect-expiry-options' => '२ होराः:2 hours,१ दिनम्:1 day,३ दिनानि:3 days,१ सप्ताहः:1 week,२ सप्ताहौ:2 weeks,१ मासः:1 month,३ मासाः:3 months,६ मासाः:6 months,१ वर्षः:1 year,अनन्तम्:infinite',
 'restriction-type' => 'अनुमतिः:',
 'restriction-level' => 'सुरक्षा-स्तरः :',
+'minimum-size' => 'कनिष्टाकारः ।',
+'maximum-size' => 'गरिष्टाकारः ।',
+'pagesize' => 'बैट्स् ।',
 
 # Restrictions (nouns)
 'restriction-edit' => 'सम्पाद्यताम्',
+'restriction-move' => 'चलनम् ।',
 'restriction-create' => 'सृज्यताम्',
 'restriction-upload' => 'आरोप्यताम्',
 
 # Restriction levels
 'restriction-level-sysop' => 'पूर्णतया संरक्षितम्',
 'restriction-level-autoconfirmed' => 'अर्धसंरक्षितम्',
+'restriction-level-all' => 'कोऽपि स्तरः ।',
 
 # Undelete
+'undelete' => 'अपमर्जितपुटानि अवलोकयतु ।',
+'undeletepage' => 'अपमर्जितपुटानि दृष्ट्वा पुनस्थापयतु ।',
+'undeletepagetitle' => "'''अधः [[:$1|$1]] इत्येतेषाम् अपनीतावृत्तीनां दर्शनं भवति ।",
+'viewdeletedpage' => 'अपमर्जितपुटानि अवलोकयतु ।',
+'undeletepagetext' => '{{PLURAL:$1|$1पुटं|$1 पुटानि}} इत्येतानि अपनीतानि किन्तु  एतानि लेखागारे सन्ति अपि च पुनस्थापितानि कर्तुं शक्यते ।',
+'undelete-fieldset-title' => 'पुनरावृत्तीः पुनस्थापयतु ।',
+'undeleteextrahelp' => "पुटानाम् इतिहासं प्रत्याहर्तुं चिह्नितमञ्जूषाः अवचिताः कृत्वा '''''{{int:undeletebtn}}''''' इत्येतत् तुदतु ।  
+विचितेतिहासं प्रत्याहर्तुं तद्वृत्तीनां पार्श्वगतचिह्नमञ्जूषासु चयनचिह्नानि विनिवेशयतु । पश्चात्'''''{{int:undeletebtn}}''''' एतत् तुदतु  ।",
+'undeleterevisions' => '$1 {{PLURAL:$1|पुनरावृत्तिः}}',
+'undeletehistory' => 'यदि भवान् पुटानि पुनस्थापयितुमिच्छति तर्हि पुनरवृत्तीनां सर्वेतिहासाः पुनस्थापितानि भवन्ति । 
+अपनयनात् परं यदि तस्मिन् एव नाम्नि नूतनपुटनिर्माणं करोति चेत् तस्य पुनस्थापितावृत्तिः पूर्वेतिहासे एव दृश्यते ।',
+'undeleterevdel' => 'यदि पुनस्थापनस्य फलस्वरूपशीर्षकपुटं, सञ्चिकां, पुनरावृत्तिं वा आंशिकरूपेण नाशयति चेत् एतत् न क्रियते ।
+एतादस्थितौ नूतनापनीताः पुनरावृत्तीनाम् अपचयनं असङ्गोपनं वा कुर्याट् ।',
+'undeletehistorynoadmin' => 'एतत्पुटम् अपमर्जितम् ।
+अधः अपमर्जनस्य कारणं दर्शितम् । अपमर्जनात् पूर्वं ये योजकाः सम्पादनं कृतवन्तः तेषां विषयः अपि दर्शिताः । 
+अपमर्जितपुनरावृत्तीनां वास्तवपाठः केवलं प्रशासकै दृष्टुं शक्यते ।',
+'undelete-revision' => '$1 ($4 इत्येतं $5 समये $3 द्वारा निर्मितम्) इत्येतेषाम् अपमर्जितपुनरावृत्तयः ।',
+'undeleterevision-missing' => 'अमान्या अथवा विलुप्ता पुनरावृत्तिः । भवान् प्रदुष्टानुबन्धयुक्तः अथवा पुनरावृत्तिः पुनस्थापिता अथवा लेखागारात् अपनीता ।',
+'undelete-nodiff' => 'पूर्वतनपुनरावृत्तिः न दृष्टा ।',
+'undeletebtn' => 'पुन्थापयतु ।',
 'undeletelink' => 'दृश्यताम्/प्रत्यानीयताम्',
 'undeleteviewlink' => 'दृश्यताम्',
+'undeletereset' => 'पुनर्योजयतु ।',
+'undeleteinvert' => 'चयनं परिवर्तयतु ।',
 'undeletecomment' => 'कारणम् :',
+'undeletedrevisions' => '{{PLURAL:$1| पुनरावृत्तिः पुनस्थापिता|$1 पुनरावृत्तयः पुनस्थापिताः}} अस्ति|सन्ति ।',
+'undeletedrevisions-files' => '{{PLURAL:$1|1 पुनरावृत्तिः|$1 पुनरावृत्तयः}} अपि च {{PLURAL:$2|१सञ्चिका|$2 सञ्चिकाः}} पुनस्थापिताः सन्ति ।',
+'undeletedfiles' => '{{PLURAL:$1|१सञ्चिका|$1 सञ्चिकाः}} पुनस्थापिताः ।',
+'cannotundelete' => 'अनपमर्जनम् असफलम् । 
+प्रथमं कोऽप्यन्यः पुटम् अपमर्जितवान् स्यात् ।',
+'undeletedpage' => "'''$1 इत्येतत् पुनस्थापितम् अस्ति । 
+सद्यः अपनीतानि पुनस्थापितानि च पुटाणि ज्ञातुम् अत्र पश्यतु । [[Special:Log/delete|अपनयनप्रवेशः]] ।",
+'undelete-header' => 'सद्यः एव अपनीतानां पुटानां दर्शनार्थं अत्र प्रविशतु । [[Special:Log/delete|अपनीतावली]] ।',
+'undelete-search-title' => 'अपमर्जितपुटानि अन्विषतु ।',
+'undelete-search-box' => 'अपमर्जितपुटानि अन्विषतु ।',
+'undelete-search-prefix' => 'इत्यनेन आरब्धपुटानि दर्शयतु ।',
 'undelete-search-submit' => 'अन्वेषणम्',
+'undelete-no-results' => 'अपमर्जनलेखागारे अमेलपुटानि लब्धानि ।',
+'undelete-filename-mismatch' => 'समयमुद्रया सञ्चिकापुनरावृत्तिः अनपमर्जितुं शक्यते । यतः $1 इति सञिकानाम अननुरूपम् ।',
+'undelete-bad-store-key' => 'समयमुद्रया सञ्चिकापुनरावृत्तिः अनपमर्जनं  नैव शक्नोति । ।$1: यतः अपमर्जनात् पूर्वमेव विलुप्तम् ।',
+'undelete-cleanup-error' => 'दोषापमारजनस्य अनुपयुक्ता लेखागारसञ्चिका "$1" ।',
+'undelete-missing-filearchive' => '$1 इति सञ्चिकालेखागारस्य अभिज्ञापकं पुनस्थापितुं नैव शक्यते । यतः एतत् दत्तपाठे नास्ति ।
+एतत् पूर्वमेव अनपमर्जितं स्यात् ।',
+'undelete-error' => 'पुटापमर्जने दोषः ।',
+'undelete-error-short' => 'सञ्चिकानपमर्जने दोषः : $1',
+'undelete-error-long' => '!!सञ्चिकानपमर्जने आगता समस्या ।$1',
+'undelete-show-file-confirm' => '$2 तः $3 मध्ये "<nowiki>$1</nowiki>" इति सञ्चिकायाः निरस्तं परिष्करणं भवान् नूनं द्रष्टुम् इच्छति ?',
 'undelete-show-file-submit' => 'आम्',
 
 # Namespace form on various pages
 'namespace' => 'नामाकाशः :',
 'invert' => 'चयनं विपरीतीकरोतु',
+'tooltip-invert' => 'चितनामस्थाने परिवर्तनं गोपयितुं मञ्जूषाम् अर्गलयतु ।',
+'namespace_association' => 'सम्बद्धं नामस्थानम् ।',
+'tooltip-namespace_association' => 'चितनामस्थानेन सह सम्बद्धं विषयनामस्थानम् अथवा सम्भाषणम् अपि उपादातुम् इमां मञ्जूषाम् अर्गलयतु ।',
 'blanknamespace' => '(मुख्यः)',
 
 # Contributions
@@ -2396,15 +2459,24 @@ $2 द्वारा सम्पादितां अन्तिमावृ
 'contributions-title' => '$1 इत्येतस्य कृते योजकानां योगदानानि',
 'mycontris' => 'मम योगदानानि',
 'contribsub2' => '$1 इत्येतदर्थम् ($2)',
+'nocontribs' => 'एतादृशयोग्यताभिः समं परिवर्तनानि न दृष्टानि ।',
 'uctop' => '(शीर्षम्)',
 'month' => 'अस्मात् मासात् (प्राक्तनानि च):',
 'year' => 'अस्मात् वर्षात् (प्राक्तनानि च):',
 
 'sp-contributions-newbies' => 'नूतनयोजकानां केवलं योगदानानि दर्श्यन्ताम्',
+'sp-contributions-newbies-sub' => 'नूतनलेखार्थम् ।',
+'sp-contributions-newbies-title' => 'नूतनलेखार्थं योजकयोगदानम् ।',
 'sp-contributions-blocklog' => 'अवरुद्धा सूची',
+'sp-contributions-deleted' => 'योजकयोगदानम् अपमर्जतु ।',
 'sp-contributions-uploads' => 'आरोप्यताम्',
 'sp-contributions-logs' => 'लोग्स',
 'sp-contributions-talk' => 'सम्भाषणम्',
+'sp-contributions-userrights' => 'योजकाधिकारस्य व्यवस्थापनम् ।',
+'sp-contributions-blocked-notice' => 'अयं प्रयोक्ता सम्प्रति अवरुद्धः वर्तते।
+नूतनतमा अवरोधाभिलेख-प्रविष्टिः सन्दर्भार्थम् अधस्तात् प्रदत्ताऽस्ति:',
+'sp-contributions-blocked-notice-anon' => 'अयं प्रयोक्ता सम्प्रति अवरुद्धः वर्तते।
+नूतनतमा अवरोधाभिलेख-प्रविष्टिः सन्दर्भार्थम् अधस्तात् प्रदत्ताऽस्ति:',
 'sp-contributions-search' => 'योगदानानि अन्विष्यन्ताम्',
 'sp-contributions-username' => 'आइ.पी.सङ्केतः अथवा योजकनाम :',
 'sp-contributions-toponly' => 'सम्पादनानां नूतनावृत्तिमात्रं दर्श्यताम्',
@@ -2416,6 +2488,7 @@ $2 द्वारा सम्पादितां अन्तिमावृ
 'whatlinkshere-page' => 'पृष्ठम् :',
 'linkshere' => "अधोलिखितानि पृष्ठाणि '''[[:$1]]''' इत्येतद् प्रति संबंधनं कुर्वन्ति :",
 'nolinkshere' => "'''[[:$1]]'''इत्येतेन न किञ्चित् पृष्ठं संयुक्तम्",
+'nolinkshere-ns' => "चितनामस्थानात्  '''[[:$1]]''' इत्येनं योजनयोग्यं पुटं नास्ति  ।",
 'isredirect' => 'अनुप्रेषण-पृष्ठम्',
 'istemplate' => 'मिलापयतु',
 'isimage' => 'सञ्चिकासंबन्ध',
@@ -2429,17 +2502,88 @@ $2 द्वारा सम्पादितां अन्तिमावृ
 'whatlinkshere-filters' => 'निस्यन्दनानि',
 
 # Block/unblock
+'autoblockid' => 'स्वयं पिहितम् । $1',
+'block' => 'योजकम् अवरुणद्धु ।',
+'unblock' => 'योजकम् अनवरुणद्धु ।',
 'blockip' => 'प्रयोक्तारं निरुध्नातु',
+'blockip-title' => 'योजकम् अवरुणद्धु ।',
+'blockip-legend' => 'योजकम् अवरुणद्धु ।',
+'blockiptext' => 'विशिष्टं  IP सङ्केतम् अथवा योजकनाम लेखानाधिकारस्य प्राप्तये निम्नदत्तपत्रस्य उपयोगं करोतु ।
+केवलं नाशकत्वम् अवरोद्धुं एतस्य उपयोगं करोतु । [[{{MediaWiki:Policy-url}}|नीतिः]] इत्यानुसारं करणीयम् ।
+अधः विशिष्टं कारणमपि लिखतु ।',
+'ipadressorusername' => 'आइ.पी.सङ्केतः अथवा योजकनाम :',
+'ipbexpiry' => 'समाप्तिः :',
 'ipbreason' => 'कारणम् :',
+'ipbreasonotherlist' => 'अन्यत् कारणम्',
+'ipbreason-dropdown' => '* अवरोधस्य सामान्यानि कारणानि ।  
+** मिथ्या योजकनाम । 
+** एकाधिकयोजकस्थानं निर्मीय तेषां दुरुपयोगः । 
+** असत्यविषयानाम् उत्तारणम् । 
+** पुटेषु अवकरपूरणम् । 
+** पुटेभ्यः पदार्थान् अपनयनम् । 
+** बाह्यजालस्थानाम् असम्बद्धानुबन्धानाम् संयोजनम् । 
+** योजकानां पीडनम् ।',
+'ipb-hardblock' => 'नामाभिलेखितयोजकान् अनेन ऐपि सङ्केतेन सम्पादनं निवारयतु ।',
+'ipbcreateaccount' => 'योजकस्थानस्य निर्माणं निवारयतु ।',
+'ipbemailban' => 'योजकस्य विद्युन्मानसन्देशप्रेषणम् अवरुणद्धु ।',
+'ipbenableautoblock' => 'अनेन योजकेन उपयुक्तम् ऐपिसङ्केतम्, अग्रे अनेन योजकेन सम्पादयितुं प्रयतमानम् ऐपिसङ्केतं च स्वयम् अवरुद्धं करोतु ।',
+'ipbsubmit' => 'एतं योजकम् अवरुणद्धु ।',
+'ipbother' => 'अन्यः समयः ।',
 'ipboptions' => '२ होराः:2 hours,१ दिनम्:1 day,३ दिनानि:3 days,१ सप्ताहः:1 week,२ सप्ताहौ:2 weeks,१ मासः:1 month,३ मासाः:3 months,६ मासाः:6 months,१ वर्षः:1 year,अनन्तम्:infinite',
 'ipbotheroption' => 'अन्य',
+'ipbotherreason' => 'अपरं/अतिरिक्तं कारणम् :',
+'ipbhidename' => 'सम्पादनेभ्यः आवलीभ्यः च योजकनाम सङ्गोपयतु ।',
+'ipbwatchuser' => 'अस्य योजकस्य योजकपुटानि सम्भाषणपुटानि च अवलोकयतु ।',
+'ipb-disableusertalk' => 'एतं योजकम् अवरोधकाले स्वस्य सम्भाषणपुटस्य सम्पानात् निवारयतु ।',
+'ipb-change-block' => 'एतैः विन्यासैः योजकं पुनः अवरुणद्धु ।',
+'ipb-confirm' => 'अवरोधं दृढयतु ।',
+'badipaddress' => 'अमान्यः ऐपिसङ्केतः ।',
+'blockipsuccesssub' => 'अवरोधः सफलः ।',
+'blockipsuccesstext' => '[[Special:Contributions/$1|$1]]इत्येतत् अवरुद्धम् । <br />
+अवरोधानां समीक्षां करोतु । [[Special:BlockList|IP अवरोधसूचिका]]',
+'ipb-blockingself' => 'भवान् स्वयम् अवरोधने निरतः । निश्चयेन स्वावरोधनम् इच्छति वा ?',
+'ipb-confirmhideuser' => 'योजकगोपनस्य पिञ्जं निपीडयन् भवान् योजकावरुद्धिं यतते । एतत् सर्वावलीषु सर्वप्रवेशसूचिकासु च योजकनाम निग्रहति । भवान् निश्चयेन एतत् कर्तुमिच्छति वा ?',
+'ipb-edit-dropdown' => 'अवरोधकारणानि सम्पादयतु ।',
+'ipb-unblock-addr' => '$1 अनवरोधनं करोतु ।',
+'ipb-unblock' => 'योजकनाम अथवा ऐपिसङ्केतम् अवरुणद्धु ।',
+'ipb-blocklist' => 'वर्तमानावरोधान् अवलोकयतु ।',
+'ipb-blocklist-contribs' => '$1कृते योगदानम् ।',
+'unblockip' => 'योजकसु अवरोधं परिहरतु ।',
+'unblockiptext' => 'सद्यः अवरुद्धान् ऐपिसङ्केतान् अथवा अवरुद्धानि योजकनामानि पुनस्संस्थाप्य लिखनावकाशं प्राप्तुम् अधो दत्तप्रपत्रस्य उपयोगं करोतु ।',
+'ipusubmit' => 'अवरोधम् अपनयतु ।',
+'unblocked' => '[[User:$1|$1]] इति योजकस्य अवरोधम् अपनयतु ।',
+'unblocked-range' => '$1 इत्येतस्य अवरोधः कृतः ।',
+'unblocked-id' => '$1 इत्यस्य अवरोधः अपनीतः ।',
+'blocklist' => 'अवरुद्धाः योजकाः ।',
 'ipblocklist' => 'अवरुद्धाः योजकाः',
+'ipblocklist-legend' => 'अवरुद्धयोजकं पश्यतु ।',
+'blocklist-userblocks' => 'योजकस्थानावरुद्धिं गोपयतु ।',
+'blocklist-tempblocks' => 'तात्कालिकावरुद्धिं गोपयतु ।',
+'blocklist-addressblocks' => 'एकाकिनम् ऐपि अवरोधं गोपयतु ।',
+'blocklist-rangeblocks' => 'प्रान्तीयावरोधान् गोपयतु ।',
+'blocklist-timestamp' => 'समयमुद्रा',
+'blocklist-target' => 'लक्ष्यम्',
+'blocklist-expiry' => 'नश्यति',
+'blocklist-by' => 'वरोधनस्य प्रशसनम् ।',
+'blocklist-params' => 'विस्तारान् अवरुणद्धु ।',
 'blocklist-reason' => 'कारणम्',
 'ipblocklist-submit' => 'अन्वेषणम्',
+'ipblocklist-localblock' => 'स्थानीयावरोधः ।',
+'ipblocklist-otherblocks' => 'अन्याः{{PLURAL:$1|अवरोधाः}}',
+'infiniteblock' => 'अनन्तम् ।',
+'expiringblock' => '$1 इत्यस्य $2समये समाप्तिः भवति ।',
+'anononlyblock' => 'अनामकः केवलम् ।',
+'noautoblockblock' => 'स्वयमवरोधः निष्क्रियः ।',
+'createaccountblock' => 'योजकस्थाननिर्माणं निष्क्रियम् ।',
+'emailblock' => 'विद्युन्मानपत्रं निष्क्रियम् ।',
+'blocklist-nousertalk' => 'स्वस्य सम्भाषणपुटं सम्पादयितुं न शक्यते ।',
+'ipblocklist-empty' => 'अवरोधावली रिक्ता अस्ति ।',
+'ipblocklist-no-results' => 'अभ्यर्थितः ऐपिसङ्केतः अथवा अभ्यर्थितः योजकनाम अवरुद्धं न ।',
 'blocklink' => 'अवरोधः क्रियताम्',
 'unblocklink' => 'निरोधः अपास्यताम्',
 'change-blocklink' => 'विभागः परिवर्त्यताम्',
 'contribslink' => 'योगदानम्',
+'emaillink' => 'विद्युन्मानपत्रं प्रेषयतु ।',
 'blocklogpage' => 'अवरोधानां सूची',
 'blocklogentry' => '[[$1]] इत्येतद् अवरुद्धम्, $2 $3 इति अवसान-समयेन सह',
 'unblocklogentry' => 'अनिरुद्धम् $1',
index a524580..f689f76 100644 (file)
@@ -587,6 +587,7 @@ $2',
 'ns-specialprotected' => 'විශේෂ පිටු සංස්කරණය කිරීම සිදු කල නොහැක.',
 'titleprotected' => "මෙම ශීර්ෂ-නාමය තැනීම  [[User:$1|$1]] විසින් වාරණය කොට ඇත.
 මේ සඳහා  ''$2''  හේතුව දක්වා ඇත.",
+'exception-nologin' => 'ප්‍රවිෂ්ට වී නොමැත',
 
 # Virus scanner
 'virus-badscanner' => "අයෝග්‍ය වික්‍යාසයකි: අඥාත වයිරස සුපිරික්සකයකි: ''$1''",
@@ -2058,6 +2059,10 @@ When filtered by user, only files where that user uploaded the most recent versi
 'allpagesbadtitle' => 'සපයා ඇති පිටු ශීර්ෂය අනීතික විය නැතහොත් එහි අන්තර්-භාෂා හෝ අන්තර් විකී උපසර්ගයක් අඩංගු විය.
 ශීර්ෂයන්හි අඩංගු විය නොහැකි අක්ෂර එකක් හෝ කිහිපයක් හෝ එහි අඩංගු වී තිබිය හැක.',
 'allpages-bad-ns' => '{{SITENAME}} හි  "$1" නාමඅවකාශය නොමැත.',
+'allpages-hide-redirects' => 'යළි-යොමු සඟවන්න',
+
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'නවතමය නරඹන්න.',
 
 # Special:Categories
 'categories' => 'ප්‍රවර්ග',
@@ -3776,6 +3781,9 @@ MediaWiki බෙදාහැර ඇත්තේ එය ප්‍රයෝජන
 'version-software' => 'ස්ථාපිත මෘදුකාංග',
 'version-software-product' => 'නිෂ්පාදනය',
 'version-software-version' => 'අනුවාදය',
+'version-entrypoints' => 'නිවේශන ලක්ෂ්‍ය URL',
+'version-entrypoints-header-entrypoint' => 'නිවේශන ලක්ෂ්‍යය',
+'version-entrypoints-header-url' => 'URL',
 
 # Special:FilePath
 'filepath' => 'ගොනු පෙත',
@@ -3927,6 +3935,7 @@ MediaWiki බෙදාහැර ඇත්තේ එය ප්‍රයෝජන
 'api-error-duplicate-archive-popup-title' => 'දැනටමත් මකා දමා ඇති එක වගේ {{PLURAL:$1|ගොනුවක්|ගොනු}}',
 'api-error-duplicate-popup-title' => 'අනුපිටපත් {{PLURAL:$1|ගොනු|ගොනුව}}',
 'api-error-empty-file' => 'ඔබ ඉදිරිපත්කල ගොනුව හිස් එකකි.',
+'api-error-emptypage' => 'නවතම එකක් තනමින්, හිස් පිටුවලට ඉඩ නොදේ.',
 'api-error-fetchfileerror' => 'අභ්‍යන්තර දෝෂය: ගොනුව පැමිණවීම අතරතුරදී කුමක්දෝ වැරදුණා.',
 'api-error-file-too-large' => 'ඔබ විසින් යොමන ලද ගොනුව පමණට වඩා විශෘලය.',
 'api-error-filename-tooshort' => 'ගොනු නාමය කෙටි වැඩියි.',
@@ -3955,6 +3964,17 @@ MediaWiki බෙදාහැර ඇත්තේ එය ප්‍රයෝජන
 'api-error-uploaddisabled' => 'මෙම විකියෙහි උඩුගතකිරීම අක්‍රිය කොට ඇත.',
 'api-error-verification-error' => 'මෙම ගොනුව පළුදුවී හෝ එයට වැරදි විස්තිර්ණයක් (extension) ඇතුවා වියහැක.',
 
+# Durations
+'duration-seconds' => '{{PLURAL:$1|තත්පර|තත්පර}} $1 ක්',
+'duration-minutes' => '{{PLURAL:$1|මිනිත්තු|මිනිත්තු}} $1 ක්',
+'duration-hours' => '{{PLURAL:$1|පැය|පැය}} $1 ක්',
+'duration-days' => '{{PLURAL:$1|දින|දින}} $1 ක්',
+'duration-weeks' => '{{PLURAL: $1|සති|සති}} $1 ක්',
+'duration-years' => '{{PLURAL:$1|වසර|වසර}} $1 ක්',
+'duration-decades' => '{{PLURAL:$1|දශක|දශක}} $1 ක්',
+'duration-centuries' => '{{PLURAL:$1|ශතවර්ෂ|ශතවර්ෂ}} $1 ක්',
+'duration-millennia' => '{{PLURAL:$1|සහස‍්‍රවර්ෂ|සහස‍්‍රවර්ෂ}} $1 ක්',
+
 # Unknown messages
 'api-error-filetype-banned-type' => '$1 යනු {{PLURAL:$4|අවසරලත් ගොනු වර්ගයක්|අවසරලත් ගොනු වර්ගයන්}} නොවේ. අවසරලත්  {{PLURAL:$3|ගොනු වර්ගය|ගොනු වර්ගයන්}} වන්නේ  $2.',
 );
index eababe7..e89ff69 100644 (file)
@@ -15,6 +15,7 @@
  * @author Eagleal
  * @author Ergon
  * @author Euriditi
+ * @author FatosMorina
  * @author Kaganer
  * @author Marinari
  * @author Mdupont
@@ -478,6 +479,10 @@ $1",
 'youhavenewmessages' => 'Ju keni $1 ($2).',
 'newmessageslink' => 'mesazhe të reja',
 'newmessagesdifflink' => 'ndryshimi i fundit',
+'youhavenewmessagesfromusers' => 'Ju keni $1 nga {{Shumës:$3|përdorues tjetër|përdoruesit $3}} ($2).',
+'youhavenewmessagesmanyusers' => 'Ju keni 1$ nga shumë përdorues (2$).',
+'newmessageslinkplural' => '{{SHUMËS:1$|një porosi e re|porosi të reja}}',
+'newmessagesdifflinkplural' => 'i fundit {{SHUMËS:$1|ndryshimi|ndryshimet}}',
 'youhavenewmessagesmulti' => 'Ju keni mesazhe të reja në $1',
 'editsection' => 'redakto',
 'editold' => 'redaktoni',
@@ -572,6 +577,8 @@ Nëse nuk është kjo arsyeja, ateherë ju mund të keni gjetur një gabim në p
 'cannotdelete' => 'Faqja ose skeda $1 nuk mund të fshihej.
 Mund të jetë fshirë nga dikush tjetër.',
 'cannotdelete-title' => 'Faqja "$1" nuk mund të fshihet',
+'delete-hook-aborted' => 'Fshirja u anulua nga togëza.
+Nuk jipet shpjegim.',
 'badtitle' => 'Titull i pasaktë',
 'badtitletext' => 'Titulli i faqes që kërkuat nuk ishte i saktë, ishte bosh, ose ishte një titull ndër-gjuhësor/inter-wiki me lidhje të pasaktë.
 Mund të përmbajë një ose më shumë germa, të cilat nuk mund të përdoren në tituj.',
@@ -603,6 +610,13 @@ $2',
 'ns-specialprotected' => 'Faqet speciale nuk mund të redaktohen.',
 'titleprotected' => "Ky titull është mbrojtur nga [[User:$1|$1]] dhe nuk mund të krijohet.
 Arsyeja e dhënë është ''$2''.",
+'filereadonlyerror' => 'Nuk është në gjendje që të ndryshojë skedarin "$1" sepse depoja e skedarit "$2" është në formën vetëm-lexim.
+
+Administratori i cili e mbylli atë e dha këtë shpjegim: "$3".',
+'invalidtitle-knownnamespace' => 'Titull jo i vlefshëm me hapësirën "$2" dhe teksti "$3"',
+'invalidtitle-unknownnamespace' => 'Titull jo i vlefshëm me numrin e panjohur të hapësirës së emrit $1 dhe tekstit "$2"',
+'exception-nologin' => 'I paqasur',
+'exception-nologin-text' => 'Kjo faqe ose ky veprim ju kërkon që të qaseni në këtë wiki.',
 
 # Virus scanner
 'virus-badscanner' => "Konfiguracion i parregullt: Skaner i panjohur virusesh: ''$1''",
@@ -623,6 +637,7 @@ Arsyeja e dhënë është ''$2''.",
 'remembermypassword' => 'Mbaj mënd fjalëkalimin tim për tërë vizitat e ardhshme (për një kohë maksimale prej $1 {{PLURAL:$1|dite|ditësh}})',
 'securelogin-stick-https' => 'Qëndro i lidhur me HTTPS pas hyrjes me emrin përkatës',
 'yourdomainname' => 'Faqja juaj',
+'password-change-forbidden' => 'Ju nuk mund të ndryshoni fjalëkalimet në këtë wiki.',
 'externaldberror' => 'Ose kishte një gabim tek regjistri i identifikimit të jashtëm, ose nuk ju lejohet të përtërini llogarinë tuaje të jashtme.',
 'login' => 'Hyni',
 'nav-login-createaccount' => 'Hyni ose hapni një llogari',
@@ -688,6 +703,7 @@ Ju lutemi ndiqni këshillat në eMailin e pranuar.',
 'emailconfirmlink' => 'Vërtetoni adresën tuaj',
 'invalidemailaddress' => 'Posta elektronike nuk mund të pranohet kështu si është pasi ka format jo valid. Ju lutemi, vendoni një postë mirë të formatuar, ose zbrazeni fushën.',
 'cannotchangeemail' => 'Adresat e-mail të llogarive nuk mund të ndryshohen në këtë wiki.',
+'emaildisabled' => 'Kjo faqe nuk mund të dërgojë e-maila.',
 'accountcreated' => 'Llogarija e Përdoruesit u krijua',
 'accountcreatedtext' => 'Llogarija e Përdoruesit për $1 u krijua',
 'createaccount-title' => 'Hapja e llogarive për {{SITENAME}}',
@@ -861,6 +877,9 @@ ose [{{fullurl:{{FULLPAGENAME}}|action=edit}} të redaktoni këtë faqe]</span>.
 Ju mundeni [[Special:Search/{{PAGENAME}}|me kërku këtë titull]] në faqe tjera,
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} me kërku në regjistrat tematikisht të afërm],
 apo [{{fullurl:{{FULLPAGENAME}}|action=edit}} me redaktu këtë faqe]</span>.',
+'missing-revision' => 'Inspektimi #$1 i faqes me emrin "{{PAGENAME}}" nuk ekziston.
+
+Kjo zakonisht shkaktuar duke ndjekur një lidhje të vjetër tek një faqe që është fshirë. Hollësitë mund të gjenden në [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} regjistrin e fshirjeve].',
 'userpage-userdoesnotexist' => 'Llogaria e përdoruesit "<nowiki>$1</nowiki>" nuk është e regjistruar. 
 Ju lutem kontrolloni nëse dëshironi të krijoni/redaktoni këtë faqe.',
 'userpage-userdoesnotexist-view' => 'Llogaria i përdoruesit "$1" nuk është e regjistruar.',
@@ -886,6 +905,7 @@ Më poshtë mund t'i referoheni shënimit të regjistruar për bllokimin e fundi
 'note' => "'''Shënim:'''",
 'previewnote' => "'''Vini re! Kjo faqe është vetëm për shqyrtim.'''
 Ndryshimet tuaja nuk janë ruajtur ende!",
+'continue-editing' => 'Vazhdo ndryshimin',
 'previewconflict' => 'Kjo parapamje reflekton tekstin sipër kutisë së redaktimit siç do të duket kur të kryeni ndryshimin.',
 'session_fail_preview' => "'''Ju kërkojmë ndjesë! Redaktimi juaj nuk mund të perpunohej për shkak të humbjes së të dhënave të seancës.'''
 Ju lutemi, provojeni përsëri.
@@ -899,6 +919,7 @@ Redaktimi nuk u pranua për të parandaluar korruptimin e tekstit në faqe.
 Kjo ndodh ndonjëherë kur përdoret server anonim dytësor me gabime.",
 'edit_form_incomplete' => "'''Disa pjesë të formularit të redaktimit nuk arritën në server; kontrolloni edhe një herë nëse redaktimet tuaja janë të paprekura dhe provojeni përsëri.'''",
 'editing' => 'Duke redaktuar $1',
+'creating' => 'Duke krijuar $1',
 'editingsection' => 'Duke redaktuar $1 (paragraf)',
 'editingcomment' => 'Duke redaktuar (paragraf i ri) $1',
 'editconflict' => 'Konflikt redaktimi: $1',
@@ -964,6 +985,7 @@ Duket se është grisur.',
 'edit-no-change' => 'Redaktimi juaj është anashkaluar pasi që asnjë ndryshim nuk u bë në tekst.',
 'edit-already-exists' => 'Faqja nuk mundej të hapet.
 Ajo tanimë ekziston.',
+'defaultmessagetext' => 'Teksti i porosisë së parazgjedhur',
 
 # Parser/template warnings
 'expensive-parserfunction-warning' => 'Kujdes: Kjo faqe ka shumë kërkesa që kërkojnë analizë gramatikore të kushtueshme për sistemin.
@@ -1120,7 +1142,7 @@ Ju lutemi kontrolloni regjistrat.',
 
 # Suppression log
 'suppressionlog' => 'Regjistri i ndalimeve',
-'suppressionlogtext' => 'Më poshtë është një listë e grisjeve dhe bllokimeve duke përfshirë përmnajtjen e fshehur nga administratorët.
+'suppressionlogtext' => 'Më poshtë është një listë e grisjeve dhe bllokimeve duke përfshirë përmbajtjen e fshehur nga administratorët.
 Shiko [[Special:BlockList|listën e bllokimeve IP]] për listën e përjashtimeve operacionale dhe bllokimeve aktuale.',
 
 # History merging
@@ -1156,6 +1178,8 @@ Kini kujdes se përdorimi i lidhjeve të shfletimit do të ndryshojë përzgjedh
 
 # Diffs
 'history-title' => 'Historiku i redaktimeve te "$1"',
+'difference-title' => 'Ndryshimi mes inspektimeve të "$1"',
+'difference-title-multipage' => 'Ndryshimi mes faqeve "$1" dhe "$2"',
 'difference-multipage' => '(Ndryshimi midis faqeve)',
 'lineno' => 'Rreshti $1:',
 'compareselectedversions' => 'Krahasoni versionet e zgjedhura',
@@ -1250,6 +1274,7 @@ Kini kujdes se përdorimi i lidhjeve të shfletimit do të ndryshojë përzgjedh
 'prefs-beta' => 'Karakteristikat Beta',
 'prefs-datetime' => 'Data dhe Ora',
 'prefs-labs' => 'Karakteristikat laboratorik',
+'prefs-user-pages' => 'Faqet e përdoruesit',
 'prefs-personal' => 'Përdoruesi',
 'prefs-rc' => 'Ndryshime së fundmi',
 'prefs-watchlist' => 'Lista mbikqyrëse',
@@ -1428,6 +1453,7 @@ Kjo informatë është publike.',
 'right-writeapi' => 'Përdorimi i shkrimit API',
 'right-delete' => 'Gris faqet',
 'right-bigdelete' => 'Gris faqet me histori të gjata',
+'right-deletelogentry' => 'Fshij dhe mos i fshij shënimet në regjistrat e veçantë',
 'right-deleterevision' => 'Grisi dhe riktheji revizionet specifike të faqeve',
 'right-deletedhistory' => 'Shiko shënimet e grisura të historikut, pa tekstet e tyre të shoqëruara',
 'right-deletedtext' => 'Shiko tekstin dhe ndryshimet e grisura ndërmjet versioneve të grisura',
@@ -1538,9 +1564,11 @@ Kjo informatë është publike.',
 'number_of_watching_users_pageview' => '[$1 duke u mbikqyrur nga {{PLURAL:$1|përdorues|përdorues}}]',
 'rc_categories' => 'Kufizimi i kategorive (të ndara me "|")',
 'rc_categories_any' => 'Të gjitha',
+'rc-change-size-new' => '$1 {{PLURAL:$1|bajt|bajtë}} pas ndryshimit',
 'newsectionsummary' => '/* $1 */ seksion i ri',
 'rc-enhanced-expand' => 'Trego detajet (kërkon JavaScript)',
 'rc-enhanced-hide' => 'Fshih detajet',
+'rc-old-title' => 'fillimisht i krijuar si "$1"',
 
 # Recent changes linked
 'recentchangeslinked' => 'Ndryshime të ndërvarura',
@@ -1688,6 +1716,7 @@ Nëse problemi vazhdon atëherë kontaktoni një [[Special:ListUsers/sysop|admin
 'upload-too-many-redirects' => 'Adresa URL përmbante shumë përcjellime.',
 'upload-unknown-size' => 'Madhësia e panjohur',
 'upload-http-error' => 'Ndodhi një gabim HTTP: $1',
+'upload-copy-upload-invalid-domain' => 'Ngarkesat e kopjimit nuk janë në dispozicion nga ky domein.',
 
 # File backend
 'backend-fail-stream' => 'Nuk mund të kalojë skedën $1.',
@@ -1706,6 +1735,12 @@ Nëse problemi vazhdon atëherë kontaktoni një [[Special:ListUsers/sysop|admin
 'backend-fail-closetemp' => 'Nuk mund të mbyllë skedën e përkohshme.',
 'backend-fail-read' => 'Nuk mund të lexojë skedën $1.',
 'backend-fail-create' => 'Nuk mund të krijojë skedën $1.',
+'backend-fail-maxsize' => 'Nuk mund të shkruante skedarin "$1" sepse ai është më i madh se {{SHUMËS:$2|një bajt|$2 bajtë}}',
+'backend-fail-readonly' => 'Shërbimi i depos "$1" është për momentin vetëm-për-lexim. Arsyeja e dhënë është: "\'\'$2\'\'"',
+'backend-fail-synced' => 'Skedari "$1" është në një gjendje të parregullt brenda proceseve të depos së brendshme',
+'backend-fail-connect' => 'Nuk u arrit lidhja me shërbimin e depos "$1".',
+'backend-fail-internal' => 'Një problem i panjohur ndodhi në shërbimin e depos "$1".',
+'backend-fail-contenttype' => 'Nuk mundi të përcaktojë llojin e përmbajtjes së skedarit për ta ruajtur në "$1".',
 
 # Lock manager
 'lockmanager-notlocked' => 'Nuk mund të zhbllokojë "$1"; nuk është e bllokuar.',
@@ -1960,6 +1995,8 @@ Secili rresht përmban lidhjet tek përcjellimi i parë dhe përcjellimi i dytë
 'wantedpages' => 'Artikuj më të dëshiruar',
 'wantedpages-badtitle' => 'Titull i pavlefshëm në vendosjen e rezultateve: $1',
 'wantedfiles' => 'Skedat e dëshiruara',
+'wantedfiletext-cat' => 'Skedarët vijues janë përdorur por nuk ekzistojnë. Skedarët nga depot e panjohura mund të listohen megjithëse nuk ekzistojnë. Ndonjë gjë pozitive e pavërtetë e tillë do të <del>largohet</del>. Për më tepër, faqet që vendosin skedarë që nuk ekzistojnë janë listuar në [[:$1]].',
+'wantedfiletext-nocat' => 'Skedarët vijues janë përdorur por nuk ekzistojnë. Skedarët nga depot e panjohura mund të listohen megjithëse nuk ekzistojnë. Ndonjë gjë pozitive e pavërtetë e tillë do të <del>largohet</del>.',
 'wantedtemplates' => 'Stampat e dëshiruara',
 'mostlinked' => 'Artikuj më të lidhur',
 'mostlinkedcategories' => 'Kategori më të lidhura',
@@ -2019,6 +2056,7 @@ Ju lutemi, vini re se faqe të tjera në rrjet si mund të lidhin një figurë m
 Ju mund të kufizoni pamje sipas tipit të regjistrit, emrit të përdoruesit (shumë i ndjeshëm), dhe faqes në çështje (edhe rastet e ndjeshme)',
 'logempty' => 'Nuk ka asnjë përputhje në regjistër.',
 'log-title-wildcard' => 'Kërko tituj që fillojnë me këtë tekst',
+'showhideselectedlogentries' => 'Paraqit/fshih shënimet e përzgjedhura të regjistruara.',
 
 # Special:AllPages
 'allpages' => 'Të gjitha faqet',
@@ -2037,6 +2075,12 @@ Ju mund të kufizoni pamje sipas tipit të regjistrit, emrit të përdoruesit (s
 'allpagesbadtitle' => 'Titulli i dhënë ishte i pavlefshë ose kishte një parashtesë ndër-gjuhe ose ndër-wiki.
 Mund të përmbajë një ose më shumë karktere të cilat nuk mund të përdoren në tituj.',
 'allpages-bad-ns' => '{{SITENAME}} nuk ka hapësirë "$1".',
+'allpages-hide-redirects' => 'Fshih përcjelljet',
+
+# SpecialCachedPage
+'cachedspecial-viewing-cached-ttl' => 'Ju jeni duke e parë një version të ruajtur të kësaj faqe, që mund të jetë deri $1 e vjetër',
+'cachedspecial-viewing-cached-ts' => 'Ju jeni duke e parë një version të ruajtur të kësaj faqe, që mund të mos jetë tërësisht e pranishme.',
+'cachedspecial-refresh-now' => 'Shikoni të fundit.',
 
 # Special:Categories
 'categories' => 'Kategori',
@@ -2272,6 +2316,8 @@ vazhdoni me kujdes.',
 'rollback' => 'Riktheji mbrapsh redaktimet',
 'rollback_short' => 'Riktheje',
 'rollbacklink' => 'riktheje',
+'rollbacklinkcount' => 'riktheni $1 {{PLURAL:$1|ndryshimin|ndryshiemt}}',
+'rollbacklinkcount-morethan' => 'riktheni më tepër $1 {{PLURAL:$1|ndryshim|ndryshime}}',
 'rollbackfailed' => 'Rikthimi dështoi',
 'cantrollback' => 'Redaktimi nuk mund të kthehej;
 redaktori i fundit është i vetmi autor i këtij artikulli.',
@@ -2706,6 +2752,7 @@ Ju lutemi zgjidhni në emët tjetër.',
 'exporttext' => 'Mund të eksportoni tekstin dhe historinë e redaktimit e një faqeje ose disa faqesh të mbështjesha në XML; kjo mund të importohet në një wiki tjetër që përdor softuerin MediaWiki (tani për tani, ky opsion nuk është përfshirë tek {{SITENAME}}).
 
 Për të eksportuar faqe, thjesht shtypni një emër për çdo rresht, ose krijoni lidhje të tipit [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] si [[{{MediaWiki:Mainpage}}]].',
+'exportall' => 'Eksportoni të gjitha faqet',
 'exportcuronly' => 'Përfshi vetëm versionin e fundit, jo të gjithë historinë',
 'exportnohistory' => "'''Shënim:''' Eksportimi i historisë së faqes për shkaqe të rendimentit nuk është e mundshme.",
 'exportlistauthors' => 'Përfshij një listë të plotë të kontribuesve për secilën faqe',
@@ -2792,6 +2839,9 @@ Ju lutemi provoni përsëri.',
 'import-invalid-interwiki' => 'Nuk mund të importohet nga wiki i specifikuar.',
 'import-error-edit' => 'Faqja "$1" nuk është importuar sepse ju nuk lejoheni ta redaktoni atë.',
 'import-error-create' => 'Faqja "$1" nuk është importuar sepse ju nuk lejoheni ta krijoni atë.',
+'import-error-interwiki' => 'Faqja "$1" nuk është importuar sepse emri i saj është rezervuar për lidhje të jashtme (interwiki)',
+'import-error-special' => 'Faqja "$1" nuk është importuar sepse ajo i përket një hapësire të veçantë që nuk i lejon faqet.',
+'import-error-invalid' => 'Faqja "$1" nuk është importuar sepse emri i saj është i palejueshëm.',
 
 # Import log
 'importlogpage' => 'Regjistri i importeve',
@@ -2802,7 +2852,15 @@ Ju lutemi provoni përsëri.',
 'import-logentry-interwiki-detail' => '$1 {{PLURAL:$!1|version|versione}} nga $2',
 
 # JavaScriptTest
+'javascripttest' => 'Duke testuar JavaScript',
+'javascripttest-disabled' => 'Ky funksion nuk është mundësuar në këtë wiki.',
+'javascripttest-title' => 'Duke kryer testet $1',
+'javascripttest-pagetext-noframework' => 'Kjo faqe është rezervuar për kryerjen e testimeve JavaScript.',
+'javascripttest-pagetext-unknownframework' => 'Kornizë pune e panjohur testuese "$1".',
+'javascripttest-pagetext-frameworks' => 'Ju lutemi zgjidhni njërën nga kornizat vijuese punuese të testimit: $1',
+'javascripttest-pagetext-skins' => "Zgjidhni një mostër për t'i kryer testimet:",
 'javascripttest-qunit-intro' => 'Shiko [$1 dokumentacionin e testimit] në mediawiki.org.',
+'javascripttest-qunit-heading' => 'Platforma testuese JavaScript QUnit',
 
 # Tooltip help for the actions
 'tooltip-pt-userpage' => 'Faqja juaj e përdoruesit',
@@ -2898,6 +2956,7 @@ Ju lutemi provoni përsëri.',
 'spambot_username' => 'MediaWiki spam-pastrues',
 'spam_reverting' => "U kthye tek versioni i fundit që s'ka lidhje tek $1",
 'spam_blanking' => 'U boshatis sepse të gjitha versionet kanë lidhje tek $1',
+'spam_deleting' => 'Të gjitha inspektimet përmbanin lidhje në $1, duke fshirë',
 
 # Info page
 'pageinfo-title' => 'Informacion për " $1 "',
@@ -3598,6 +3657,7 @@ Ju duhet të keni marrë [{{SERVER}}{{SCRIPTPATH}}/COPYING një kopje të GNU Ge
 'version-software' => 'Softuerët e instaluar',
 'version-software-product' => 'Produkti',
 'version-software-version' => 'Versioni',
+'version-entrypoints-header-url' => 'URL',
 
 # Special:FilePath
 'filepath' => 'Vendndodhja e skedave',
@@ -3759,6 +3819,8 @@ Përndryshe, ju mund të formularin e thjeshtë më poshtë. Komenti juaj do të
 'api-error-empty-file' => 'Skeda që paraqitët ishte bosh.',
 'api-error-emptypage' => 'Nuk lejohet krijimi i faqeve të reja bosh.',
 'api-error-fetchfileerror' => 'Gabim i brendshëm: Diçka shkoi keq gjatë marrjes së skedës.',
+'api-error-fileexists-forbidden' => 'Një skedar me emrin "$1" tashmë ekziston dhe nuk mund të mbishkruhet.',
+'api-error-fileexists-shared-forbidden' => 'Një skedar me emrin "$1" tashmë ekziston në depon për skedarët e shpërndarë dhe nuk mund të mbishkruhet.',
 'api-error-file-too-large' => 'Skeda që paraqitët ishte shumë e madhe.',
 'api-error-filename-tooshort' => 'Emri i skedës është shumë i shkurtër.',
 'api-error-filetype-banned' => 'Ky lloj i skedës është përjashtuar.',
@@ -3786,6 +3848,17 @@ Përndryshe, ju mund të formularin e thjeshtë më poshtë. Komenti juaj do të
 'api-error-uploaddisabled' => 'Ngarkimi është i çaktivizuar në këte wiki.',
 'api-error-verification-error' => 'Skeda mund të jetë e korruptuar ose ka shtesë të gabuar.',
 
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|sekondë|sekonda}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minutë|minuta}}',
+'duration-hours' => '$1 {{PLURAL:$1|orë|orë}}',
+'duration-days' => '$1 {{PLURAL:$1|ditë|ditë}}',
+'duration-weeks' => '$1 {{PLURAL:$1|javë|javë}}',
+'duration-years' => '$1 {{PLURAL:$1|vit|vite}}',
+'duration-decades' => '$1 {{PLURAL:$1|dekadë|dekada}}',
+'duration-centuries' => '$1 {{PLURAL:$1|shekull|shekuj}}',
+'duration-millennia' => '$1 {{PLURAL:$1|milennium|mileniume}}',
+
 # Unknown messages
 'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|nuk është një lloj i skedës së lejuar|nuk janë lloje të lejuara të skedave}}. {{PLURAL:$3|Lloji i lejuar i skedës është|Llojet e lejuara të skedave janë}} $2.',
 );
index acb2eca..4862b9c 100644 (file)
@@ -414,17 +414,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Уређивање одељака десним кликом на њихове наслове (јаваскрипт)',
 'tog-showtoc' => 'Прикажи садржај страница које имају више од три поднаслова',
 'tog-rememberpassword' => 'Запамти ме на овом прегледачу (најдуже $1 {{PLURAL:$1|дан|дана|дана}})',
-'tog-watchcreations' => 'Додај странице које направим у списак надгледања',
-'tog-watchdefault' => 'Додај странице које изменим у списак надгледања',
-'tog-watchmoves' => 'Додај странице које преместим у списак надгледања',
-'tog-watchdeletion' => 'Додај странице које обришем у списак надгледања',
+'tog-watchcreations' => 'Додај странице које направим и датотеке које пошаљем у списак надгледања',
+'tog-watchdefault' => 'Ð\94одаÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86е Ð¸ Ð´Ð°Ñ\82оÑ\82еке ÐºÐ¾Ñ\98е Ð¸Ð·Ð¼ÐµÐ½Ð¸Ð¼ Ñ\83 Ñ\81пиÑ\81ак Ð½Ð°Ð´Ð³Ð»ÐµÐ´Ð°Ñ\9aа',
+'tog-watchmoves' => 'Ð\94одаÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86е Ð¸ Ð´Ð°Ñ\82оÑ\82еке ÐºÐ¾Ñ\98е Ð¿Ñ\80емеÑ\81Ñ\82им Ñ\83 Ñ\81пиÑ\81ак Ð½Ð°Ð´Ð³Ð»ÐµÐ´Ð°Ñ\9aа',
+'tog-watchdeletion' => 'Ð\94одаÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86е Ð¸ Ð´Ð°Ñ\82оÑ\82еке ÐºÐ¾Ñ\98е Ð¾Ð±Ñ\80иÑ\88ем Ñ\83 Ñ\81пиÑ\81ак Ð½Ð°Ð´Ð³Ð»ÐµÐ´Ð°Ñ\9aа',
 'tog-minordefault' => 'Означавај све измене као мање',
 'tog-previewontop' => 'Прикажи преглед пре оквира за уређивање',
 'tog-previewonfirst' => 'Прикажи преглед на првој измени',
 'tog-nocache' => 'Онемогући привремено меморисање страница',
-'tog-enotifwatchlistpages' => 'Пошаљи ми е-поруку када се промени страница коју надгледам',
+'tog-enotifwatchlistpages' => 'Ð\9fоÑ\88аÑ\99и Ð¼Ð¸ Ðµ-поÑ\80Ñ\83кÑ\83 ÐºÐ°Ð´Ð° Ñ\81е Ð¿Ñ\80омени Ñ\81Ñ\82Ñ\80аниÑ\86а Ð¸Ð»Ð¸ Ð´Ð°Ñ\82оÑ\82ека ÐºÐ¾Ñ\98Ñ\83 Ð½Ð°Ð´Ð³Ð»ÐµÐ´Ð°Ð¼',
 'tog-enotifusertalkpages' => 'Пошаљи ми е-поруку када се промени моја страница за разговор',
-'tog-enotifminoredits' => 'Пошаљи ми е-поруку и за мање измене',
+'tog-enotifminoredits' => 'Пошаљи ми е-поруку и за мање измене у страницама и датотекама',
 'tog-enotifrevealaddr' => 'Откриј моју е-адресу у порукама обавештења',
 'tog-shownumberswatching' => 'Прикажи број корисника који надгледају',
 'tog-oldsig' => 'Текући потпис:',
@@ -669,7 +669,11 @@ $1',
 'retrievedfrom' => 'Преузето из „$1“',
 'youhavenewmessages' => 'Имате $1 ($2).',
 'newmessageslink' => 'нових порука',
-'newmessagesdifflink' => 'последња измена',
+'newmessagesdifflink' => 'последњу измену',
+'youhavenewmessagesfromusers' => 'Имате $1 од {{PLURAL:$3|другог корисника|$3 корисника|$3 корисника}} ($2).',
+'youhavenewmessagesmanyusers' => 'Имате $1 од много корисника ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|нову поруку|нове поруке}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|последњу измену|последње измене}}',
 'youhavenewmessagesmulti' => 'Имате нових порука на $1',
 'editsection' => 'уреди',
 'editsection-brackets' => '[$1]',
@@ -826,6 +830,7 @@ $2',
 'remembermypassword' => 'Запамти ме на овом прегледачу (најдуже $1 {{PLURAL:$1|дан|дана|дана}})',
 'securelogin-stick-https' => 'Останите повезани са HTTPS након пријаве',
 'yourdomainname' => 'Домен:',
+'password-change-forbidden' => 'Не можете да промените лозинку на овом викију.',
 'externaldberror' => 'Дошло је до грешке при препознавању базе података или немате овлашћења да ажурирате свој спољни налог.',
 'login' => 'Пријави ме',
 'nav-login-createaccount' => 'Пријава/регистрација',
@@ -1007,7 +1012,7 @@ $2
 'watchthis' => 'надгледај ову страницу',
 'savearticle' => 'Сачувај страницу',
 'preview' => 'Преглед',
-'showpreview' => 'Ð\9fÑ\80икажи Ð¿Ñ\80еÑ\82пÑ\80еглед',
+'showpreview' => 'Ð\9fÑ\80егледаÑ\98',
 'showlivepreview' => 'Тренутни преглед',
 'showdiff' => 'Прикажи измене',
 'anoneditwarning' => "'''Упозорење:''' нисте пријављени.
@@ -1076,6 +1081,10 @@ $2
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} уредити страницу]</span>.',
 'noarticletext-nopermission' => 'На овој страници тренутно нема садржаја.
 Можете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама или <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје]</span>.',
+'missing-revision' => 'Не могу да пронађем измену бр. $1 на страници под називом „{{PAGENAME}}“.
+
+Ово се обично дешава када пратите застарелу везу до странице која је обрисана.
+Више информација можете пронаћи у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневнику брисања].',
 'userpage-userdoesnotexist' => 'Кориснички налог „<nowiki>$1</nowiki>“ није отворен.
 Размислите да ли заиста желите да направите или уредите ову страницу.',
 'userpage-userdoesnotexist-view' => 'Кориснички налог „$1“ није отворен.',
@@ -1170,7 +1179,7 @@ $2
 'sectioneditnotsupported-text' => 'Уређивање одељка није подржано на овој страници.',
 'permissionserrors' => 'Грешке у дозволама',
 'permissionserrorstext' => 'Немате овлашћење за ту радњу из {{PLURAL:$1|следећег|следећих}} разлога:',
-'permissionserrorstext-withaction' => 'Ð\9dемаÑ\82е Ð¾Ð²Ð»Ð°Ñ\88Ñ\9bеÑ\9aа Ð·Ð° $2 Ð·Ð±Ð¾Ð³ {{PLURAL:$1|следећег|следећих}} разлога:',
+'permissionserrorstext-withaction' => 'Ð\9dемаÑ\82е Ð´Ð¾Ð·Ð²Ð¾Ð»Ñ\83 Ð´Ð° $2 Ð¸Ð· {{PLURAL:$1|следећег|следећих}} разлога:',
 'recreate-moveddeleted-warn' => "'''Упозорење: поново правите страницу која је претходно обрисана.'''
 
 Размотрите да ли је прикладно да наставите с уређивањем ове странице.
@@ -1208,6 +1217,7 @@ $2
 'expansion-depth-exceeded-warning' => 'Страница у којој је прекорачена дубина проширења',
 'parser-unstrip-loop-warning' => 'Утврђена је петља',
 'parser-unstrip-recursion-limit' => 'Прекорачено је ограничење рекурзије ($1)',
+'converter-manual-rule-error' => 'Пронађена је грешка у правилу за ручно претварање језика',
 
 # "Undo" feature
 'undo-success' => 'Измена се може вратити.
@@ -1397,6 +1407,10 @@ $1",
 'editundo' => 'поништи',
 'diff-multi' => '({{PLURAL:$1|није приказана међуизмена|нису приказане $1 међуизмене|није приказано $1 међуизмена}} {{PLURAL:$2|једног|$2|$2}} корисника)',
 'diff-multi-manyusers' => '({{PLURAL:$1|Није приказана међуизмена|Нису приказане $1 међуизмене|Није приказано $1 међуизмена}} од више од $2 корисника)',
+'difference-missing-revision' => 'Не могу да пронађем {{PLURAL:$2|једну измену|$2 измене|$2 измена}} од ове разлике ($1).
+
+Ово се обично дешава када пратите застарелу везу до странице која је обрисана.
+Више информација можете пронаћи у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дневнику брисања].',
 
 # Search results
 'searchresults' => 'Резултати претраге',
@@ -1671,6 +1685,7 @@ $1",
 'right-writeapi' => 'писање АПИ-ја',
 'right-delete' => 'брисање страница',
 'right-bigdelete' => 'брисање страница с великом историјом',
+'right-deletelogentry' => 'Брисање и враћање одређених ставки у дневнику',
 'right-deleterevision' => 'брисање и враћање одређених измена страница',
 'right-deletedhistory' => 'прегледање обрисаних ставки историје без повезаног текста',
 'right-deletedtext' => 'прегледање обрисаног текста и измена између обрисаних измена',
@@ -1679,7 +1694,7 @@ $1",
 'right-suppressrevision' => 'прегледање и враћање измена које су сакривене од стране администратора',
 'right-suppressionlog' => 'гледање приватних дневника',
 'right-block' => 'блокирање даљих измена других корисника',
-'right-blockemail' => 'блокиÑ\80аÑ\9aе ÐºÐ¾Ñ\80иÑ\81ника да шаљу е-поруке',
+'right-blockemail' => 'онемогÑ\83Ñ\9bаваÑ\9aе ÐºÐ¾Ñ\80иÑ\81ниÑ\86има да шаљу е-поруке',
 'right-hideuser' => 'блокирање корисничког имена и његово сакривање од јавности',
 'right-ipblock-exempt' => 'заобилажење блокирања IP адресе, самоблокирања и блокирања опсега',
 'right-proxyunbannable' => 'заобилажење самоблокирања посредника',
@@ -2199,9 +2214,9 @@ $1',
 
 'disambiguations' => 'Странице до вишезначних одредница',
 'disambiguationspage' => 'Template:Вишезначна одредница',
-'disambiguations-text' => "Следеће странице су повезане с '''вишезначном одредницом'''.
\9eне Ð±Ð¸ Ñ\82Ñ\80ебало Ð±Ð¸Ñ\82и Ñ\83пÑ\83Ñ\9bене ÐºÐ° Ð¾Ð´Ð³Ð¾Ð²Ð°Ñ\80аÑ\98Ñ\83Ñ\9bем Ñ\87ланкÑ\83.
-Страница се сматра вишезначном одредницом ако користи шаблон који је повезан са списком [[MediaWiki:Disambiguationspage]].",
+'disambiguations-text' => "Следеће странице садрже бар једну везу до '''вишезначне одреднице'''.
£Ð¼ÐµÑ\81Ñ\82о Ñ\82ога, Ð²Ð°Ñ\99ало Ð±Ð¸ Ð´Ð° Ð²Ð¾Ð´Ðµ Ð´Ð¾ Ð¾Ð´Ð³Ð¾Ð²Ð°Ñ\80аÑ\98Ñ\83Ñ\9bе Ñ\82еме.
+Страница се сматра вишезначном одредницом ако користи шаблон који води од [[MediaWiki:Disambiguationspage]].",
 
 'doubleredirects' => 'Двострука преусмерења',
 'doubleredirectstext' => 'Ова страница приказује странице које преусмеравају на друга преусмерења.
@@ -2533,7 +2548,7 @@ $UNWATCHURL
 Погледајте ''$2'' за више детаља.",
 'dellogpage' => 'Дневник брисања',
 'dellogpagetext' => 'Испод је списак последњих брисања.',
-'deletionlog' => 'Ð\94невник брисања',
+'deletionlog' => 'дневник брисања',
 'reverted' => 'Враћено на ранију измену',
 'deletecomment' => 'Разлог:',
 'deleteotherreason' => 'Други/додатни разлог:',
@@ -2552,6 +2567,8 @@ $UNWATCHURL
 'rollback' => 'Врати измене',
 'rollback_short' => 'Врати',
 'rollbacklink' => 'врати',
+'rollbacklinkcount' => 'врати $1 {{PLURAL:$1|измену|измене|измена}}',
+'rollbacklinkcount-morethan' => 'врати више од $1 {{PLURAL:$1|измене|измене|измена}}',
 'rollbackfailed' => 'Неуспешно враћање',
 'cantrollback' => 'Не могу да вратим измену.
 Последњи аутор је уједно и једини.',
@@ -2748,7 +2765,7 @@ $1',
 # Block/unblock
 'autoblockid' => 'Самоблокирање #$1',
 'block' => 'Блокирај корисника',
-'unblock' => 'Ð\9eдблокирај корисника',
+'unblock' => 'Ð\94еблокирај корисника',
 'blockip' => 'Блокирај корисника',
 'blockip-title' => 'Блокирање корисника',
 'blockip-legend' => 'Блокирај корисника',
@@ -2769,7 +2786,7 @@ $1',
 ** Неприхватљиво корисничко име',
 'ipb-hardblock' => 'Забрани пријављеним корисницима да уређују с ове ИП адресе',
 'ipbcreateaccount' => 'Онемогући отварање налога',
-'ipbemailban' => 'Ð\97абÑ\80ани ÐºÐ¾Ñ\80иÑ\81никÑ\83 Ñ\81лаÑ\9aе Ðµ-поÑ\80Ñ\83ка',
+'ipbemailban' => 'Ð\9eнемогÑ\83Ñ\9bи ÐºÐ¾Ñ\80иÑ\81никÑ\83 Ð´Ð° Ñ\88аÑ\99е Ðµ-поÑ\80Ñ\83ке',
 'ipbenableautoblock' => 'Аутоматски блокирај последњу ИП адресу овог корисника и све даљње адресе с којих покуша да уређује',
 'ipbsubmit' => 'Блокирај овог корисника',
 'ipbother' => 'Друго време:',
@@ -2788,11 +2805,11 @@ $1',
 'ipb-blockingself' => 'Овом радњом ћете блокирати себе! Јесте ли сигурни да то желите?',
 'ipb-confirmhideuser' => 'Управо ћете блокирати корисника с укљученом могућношћу „сакриј корисника“. Овим ће корисничко име бити сакривено у свим списковима и извештајима. Желите ли то да урадите?',
 'ipb-edit-dropdown' => 'Уреди разлоге блокирања',
-'ipb-unblock-addr' => 'Ð\9eдблокирај $1',
-'ipb-unblock' => 'Ð\9eдблокирај корисничко име или ИП адресу',
+'ipb-unblock-addr' => 'Ð\94еблокирај $1',
+'ipb-unblock' => 'Ð\94еблокирај корисничко име или ИП адресу',
 'ipb-blocklist' => 'Погледај постојећа блокирања',
 'ipb-blocklist-contribs' => 'Доприноси за $1',
-'unblockip' => 'Ð\9eдблокирај корисника',
+'unblockip' => 'Ð\94еблокирај корисника',
 'unblockiptext' => 'Користите образац испод да бисте вратили право писања блокираној IP адреси или корисничком имену.',
 'ipusubmit' => 'Уклони ову блокаду',
 'unblocked' => '[[User:$1|$1]] је деблокиран',
@@ -2824,7 +2841,7 @@ $1',
 'ipblocklist-empty' => 'Списак блокирања је празан.',
 'ipblocklist-no-results' => 'Тражена ИП адреса или корисничко име није блокирано.',
 'blocklink' => 'блокирај',
-'unblocklink' => 'одблокирај',
+'unblocklink' => 'деблокирај',
 'change-blocklink' => 'промени блокирање',
 'contribslink' => 'доприноси',
 'emaillink' => 'пошаљи е-поруку',
@@ -2840,7 +2857,7 @@ $1',
 'blocklogtext' => 'Ово је дневник блокирања и деблокирања корисника.
 Аутоматски блокиране ИП адресе нису наведене.
 Текуће забране и блокирања можете наћи [[Special:BlockList|овде]].',
-'unblocklogentry' => '{{GENDER:|Ñ\98е Ð¾Ð´Ð±Ð»Ð¾ÐºÐ¸Ñ\80ао|Ñ\98е Ð¾Ð´Ð±Ð»Ð¾ÐºÐ¸Ñ\80ала|Ñ\98е Ð¾Ð´блокирао}} „$1“',
+'unblocklogentry' => '{{GENDER:|Ñ\98е Ð´ÐµÐ±Ð»Ð¾ÐºÐ¸Ñ\80ао|Ñ\98е Ð´ÐµÐ±Ð»Ð¾ÐºÐ¸Ñ\80ала|Ñ\98е Ð´Ðµблокирао}} „$1“',
 'block-log-flags-anononly' => 'само анонимни корисници',
 'block-log-flags-nocreate' => 'онемогућено отварање налога',
 'block-log-flags-noautoblock' => 'аутоматско блокирање је онемогућено',
@@ -3178,7 +3195,7 @@ $1',
 'tooltip-watchlistedit-raw-submit' => 'Ажурирај списак',
 'tooltip-recreate' => 'Поново направите страницу иако је обрисана',
 'tooltip-upload' => 'Започните отпремање',
-'tooltip-rollback' => '„Врати“ поништава последњу измену ове странице једним кликом',
+'tooltip-rollback' => 'Опција „Врати“ враћа измене последњег корисника',
 'tooltip-undo' => 'Враћа ову измену и отвара образац за уређивање.',
 'tooltip-preferences-save' => 'Сачувај поставке',
 'tooltip-summary' => 'Унесите кратак опис',
@@ -4337,5 +4354,5 @@ $5
 'duration-millennia' => '$1 {{PLURAL:$1|миленијум|миленијума|миленијума}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|је забрањена врста датотеке|су забрањене врсте датотека}}. {{PLURAL:$3|Дозвољена врста датотеке је|Дозвољене врсте датотека су}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|је забрањена врста датотеке|су забрањене врсте датотека}}. {{PLURAL:$3|Дозвољена је|Дозвољене су}} $2.',
 );
index d5a31a1..5d72b53 100644 (file)
@@ -314,7 +314,7 @@ $messages = array(
 'tog-hidepatrolled' => 'Sakrij pregledane izmene u spisku skorašnjih izmena',
 'tog-newpageshidepatrolled' => 'Sakrij pregledane stranice sa spiska novih stranica',
 'tog-extendwatchlist' => 'Proširi spisak nadgledanja za prikaz svih izmena, ne samo skorašnjih',
-'tog-usenewrc' => 'Poboljšani spisak skorašnjih izmena (javaskript)',
+'tog-usenewrc' => 'Promene u grupi po stranici u spisku skorašnjih izmena i nadgledanih stranica (zahteva javaskript)',
 'tog-numberheadings' => 'Samostalno numeriši podnaslove',
 'tog-showtoolbar' => 'Traka s alatkama za uređivanje (javaskript)',
 'tog-editondblclick' => 'Uređivanje stranica dvostrukim klikom (javaskript)',
@@ -322,17 +322,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Uređivanje odeljaka desnim klikom na njihove naslove (javaskript)',
 'tog-showtoc' => 'Prikaži sadržaj stranica koje imaju više od tri podnaslova',
 'tog-rememberpassword' => 'Zapamti me na ovom pregledaču (najduže $1 {{PLURAL:$1|dan|dana|dana}})',
-'tog-watchcreations' => 'Dodaj stranice koje napravim u spisak nadgledanja',
-'tog-watchdefault' => 'Dodaj stranice koje izmenim u spisak nadgledanja',
-'tog-watchmoves' => 'Dodaj stranice koje premestim u spisak nadgledanja',
-'tog-watchdeletion' => 'Dodaj stranice koje obrišem u spisak nadgledanja',
+'tog-watchcreations' => 'Dodaj stranice koje napravim i datoteke koje pošaljem u spisak nadgledanja',
+'tog-watchdefault' => 'Dodaj stranice i datoteke koje izmenim u spisak nadgledanja',
+'tog-watchmoves' => 'Dodaj stranice i datoteke koje premestim u spisak nadgledanja',
+'tog-watchdeletion' => 'Dodaj stranice i datoteke koje obrišem u spisak nadgledanja',
 'tog-minordefault' => 'Označavaj sve izmene kao manje',
 'tog-previewontop' => 'Prikaži pregled pre okvira za uređivanje',
 'tog-previewonfirst' => 'Prikaži pregled na prvoj izmeni',
 'tog-nocache' => 'Onemogući privremeno memorisanje stranica',
-'tog-enotifwatchlistpages' => 'Pošalji mi e-poruku kada se promeni stranica koju nadgledam',
+'tog-enotifwatchlistpages' => 'Pošalji mi e-poruku kada se promeni stranica ili datoteka koju nadgledam',
 'tog-enotifusertalkpages' => 'Pošalji mi e-poruku kada se promeni moja stranica za razgovor',
-'tog-enotifminoredits' => 'Pošalji mi e-poruku i za manje izmene',
+'tog-enotifminoredits' => 'Pošalji mi e-poruku i za manje izmene u stranicama i datotekama',
 'tog-enotifrevealaddr' => 'Otkrij moju e-adresu u porukama obaveštenja',
 'tog-shownumberswatching' => 'Prikaži broj korisnika koji nadgledaju',
 'tog-oldsig' => 'Tekući potpis:',
@@ -489,7 +489,7 @@ $messages = array(
 'history' => 'Istorija stranice',
 'history_short' => 'Istorija',
 'updatedmarker' => 'ažurirano od moje poslednje posete',
-'printableversion' => 'Izdanje za štampu',
+'printableversion' => 'Verzija za štampu',
 'permalink' => 'Trajna veza',
 'print' => 'Štampaj',
 'view' => 'Pogledaj',
@@ -577,7 +577,11 @@ Pogledajte stranicu za [[Special:Version|izdanje]].',
 'retrievedfrom' => 'Preuzeto iz „$1“',
 'youhavenewmessages' => 'Imate $1 ($2).',
 'newmessageslink' => 'novih poruka',
-'newmessagesdifflink' => 'poslednja izmena',
+'newmessagesdifflink' => 'poslednju izmenu',
+'youhavenewmessagesfromusers' => 'Imate $1 od {{PLURAL:$3|drugog korisnika|$3 korisnika|$3 korisnika}} ($2).',
+'youhavenewmessagesmanyusers' => 'Imate $1 od mnogo korisnika ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|novu poruku|nove poruke}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|poslednju izmenu|poslednje izmene}}',
 'youhavenewmessagesmulti' => 'Imate novih poruka na $1',
 'editsection' => 'uredi',
 'editsection-brackets' => '[$1]',
@@ -632,13 +636,13 @@ Spisak svih posebnih stranica nalazi se [[Special:SpecialPages|ovde]].',
 # General errors
 'error' => 'Greška',
 'databaseerror' => 'Greška u bazi podataka',
-'dberrortext' => 'Došlo je do sintaksne greške u bazi.
+'dberrortext' => 'Došlo je do sintaktičke greške u bazi.
 Možda se radi o grešci u softveru.
 Poslednji pokušaj upita je glasio:
 <blockquote><tt>$1</tt></blockquote>
 unutar funkcije „<tt>$2</tt>“.
 Baza podataka je prijavila grešku „<tt>$3: $4</tt>“.',
-'dberrortextcl' => 'Došlo je do sintaksne greške u bazi.
+'dberrortextcl' => 'Došlo je do sintaktičke greške u bazi.
 Poslednji pokušaj upita je glasio:
 „$1“
 unutar funkcije „$2“.
@@ -674,6 +678,8 @@ Prijavite je [[Special:ListUsers/sysop|administratoru]] uz odgovarajuću vezu.',
 'cannotdelete' => 'Ne mogu da obrišem stranicu ili datoteku „$1“.
 Verovatno ju je neko drugi obrisao.',
 'cannotdelete-title' => 'Ne mogu da obrišem stranicu „$1“',
+'delete-hook-aborted' => 'Brisanje je prekinula kuka.
+Nije dato nikakvo obrazloženje.',
 'badtitle' => 'Neispravan naslov',
 'badtitletext' => 'Naslov stranice je neispravan, prazan ili je međujezički ili međuviki naslov pogrešno povezan.
 Možda sadrži znakove koji se ne mogu koristiti u naslovima.',
@@ -709,6 +715,8 @@ Navedeni razlog: ''$2''.",
 Administrator koji ju je zaključao ponudio je sledeće objašnjenje: „$3“.',
 'invalidtitle-knownnamespace' => 'Neispravan naslov s imenskim prostorom „$2“ i tekstom „$3“',
 'invalidtitle-unknownnamespace' => 'Neispravan naslov s imenskim prostorom br. $1 i tekstom „$2“',
+'exception-nologin' => 'Niste prijavljeni',
+'exception-nologin-text' => 'Ova stranica ili radnja zahteva da budete prijavljeni na viki.',
 
 # Virus scanner
 'virus-badscanner' => "Neispravna postavka: nepoznati skener za viruse: ''$1''",
@@ -730,6 +738,7 @@ Ne zaboravite da prilagodite svoja [[Special:Preferences|podešavanja]].',
 'remembermypassword' => 'Zapamti me na ovom pregledaču (najduže $1 {{PLURAL:$1|dan|dana|dana}})',
 'securelogin-stick-https' => 'Ostanite povezani sa HTTPS nakon prijave',
 'yourdomainname' => 'Domen:',
+'password-change-forbidden' => 'Ne možete da promenite lozinku na ovom vikiju.',
 'externaldberror' => 'Došlo je do greške pri prepoznavanju baze podataka ili nemate ovlašćenja da ažurirate svoj spoljni nalog.',
 'login' => 'Prijavi me',
 'nav-login-createaccount' => 'Prijava/registracija',
@@ -911,7 +920,7 @@ Privremena lozinka: $2',
 'watchthis' => 'nadgledaj ovu stranicu',
 'savearticle' => 'Sačuvaj stranicu',
 'preview' => 'Pregled',
-'showpreview' => 'Prikaži pregled',
+'showpreview' => 'Pregledaj',
 'showlivepreview' => 'Trenutni pregled',
 'showdiff' => 'Prikaži izmene',
 'anoneditwarning' => "'''Upozorenje:''' niste prijavljeni.
@@ -980,6 +989,10 @@ Možete [[Special:Search/{{PAGENAME}}|potražiti ovaj naslov]] na drugim stranic
 <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti srodne izveštaje] ili [{{fullurl:{{FULLPAGENAME}}|action=edit}} urediti stranicu]</span>.',
 'noarticletext-nopermission' => 'Na ovoj stranici trenutno nema sadržaja.
 Možete [[Special:Search/{{PAGENAME}}|potražiti ovaj naslov]] na drugim stranicama ili <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti srodne izveštaje]</span>.',
+'missing-revision' => 'Ne mogu da pronađem izmenu br. $1 na stranici pod nazivom „{{PAGENAME}}“.
+
+Ovo se obično dešava kada pratite zastarelu vezu do stranice koja je obrisana.
+Više informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} dnevniku brisanja].',
 'userpage-userdoesnotexist' => 'Korisnički nalog „<nowiki>$1</nowiki>“ nije otvoren.
 Razmislite da li zaista želite da napravite ili uredite ovu stranicu.',
 'userpage-userdoesnotexist-view' => 'Korisnički nalog „$1“ nije otvoren.',
@@ -1082,8 +1095,8 @@ Ovde je navedena istorija brisanja i premeštanja s obrazloženjem:",
 'moveddeleted-notice' => 'Ova stranica je obrisana.
 Istorija njenog brisanja i premeštanja nalazi se ispod:',
 'log-fulllog' => 'Pogledaj celu istoriju',
-'edit-hook-aborted' => 'Izmena je prekinuta kukom.
-Obrazloženje nije ponuđeno.',
+'edit-hook-aborted' => 'Izmenu je prekinula kuka.
+Nije dato nikakvo obrazloženje.',
 'edit-gone-missing' => 'Ne mogu da ažuriram stranicu.
 Izgleda da je obrisana.',
 'edit-conflict' => 'Sukob izmena.',
@@ -1112,6 +1125,7 @@ Ovakve argumente bi trebalo izbegavati.",
 'expansion-depth-exceeded-warning' => 'Stranica u kojoj je prekoračena dubina proširenja',
 'parser-unstrip-loop-warning' => 'Utvrđena je petlja',
 'parser-unstrip-recursion-limit' => 'Prekoračeno je ograničenje rekurzije ($1)',
+'converter-manual-rule-error' => 'Pronađena je greška u pravilu za ručno pretvaranje jezika',
 
 # "Undo" feature
 'undo-success' => 'Izmena se može vratiti.
@@ -1153,7 +1167,7 @@ Objašnjenje: '''({{int:cur}})''' – razlika s trenutnom izmenom,
 
 # Revision feed
 'history-feed-title' => 'Istorija izmena',
-'history-feed-description' => 'Istorija izmena ove stranice',
+'history-feed-description' => 'Istorija izmena ove stranice na vikiju',
 'history-feed-item-nocomment' => '$1 u $2',
 'history-feed-empty' => 'Tražena stranica ne postoji.
 Moguće da je obrisana s vikija ili je preimenovana.
@@ -1291,8 +1305,8 @@ Korišćenje navigacionih veza će poništiti ovu kolonu.',
 'mergelogpagetext' => 'Ispod se nalazi spisak skorašnjih spajanja istorija stranica.',
 
 # Diffs
-'history-title' => 'Istorija izmena za „$1“',
-'difference-title' => 'Razlika između izmena stranice „$1“',
+'history-title' => 'Istorija izmena stranice „$1“',
+'difference-title' => 'Razlika između izmena na stranici „$1“',
 'difference-title-multipage' => 'Razlika između stranica „$1“ i „$2“',
 'difference-multipage' => '(razlike između stranica)',
 'lineno' => 'Red $1:',
@@ -1301,6 +1315,10 @@ Korišćenje navigacionih veza će poništiti ovu kolonu.',
 'editundo' => 'poništi',
 'diff-multi' => '({{PLURAL:$1|nije prikazana međuizmena|nisu prikazane $1 međuizmene|nije prikazano $1 međuizmena}} {{PLURAL:$2|jednog|$2|$2}} korisnika)',
 'diff-multi-manyusers' => '({{PLURAL:$1|Nije prikazana međuizmena|Nisu prikazane $1 međuizmene|Nije prikazano $1 međuizmena}} od više od $2 korisnika)',
+'difference-missing-revision' => 'Ne mogu da pronađem {{PLURAL:$2|jednu izmenu|$2 izmene|$2 izmena}} od ove razlike ($1).
+
+Ovo se obično dešava kada pratite zastarelu vezu do stranice koja je obrisana.
+Više informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} dnevniku brisanja].',
 
 # Search results
 'searchresults' => 'Rezultati pretrage',
@@ -1391,6 +1409,7 @@ Upamtite da njegovi popisi ovog vikija mogu biti zastareli.',
 'prefs-beta' => 'Beta mogućnosti',
 'prefs-datetime' => 'Datum i vreme',
 'prefs-labs' => 'Probne mogućnosti',
+'prefs-user-pages' => 'Korisničke stranice',
 'prefs-personal' => 'Profil',
 'prefs-rc' => 'Skorašnje izmene',
 'prefs-watchlist' => 'Spisak nadgledanja',
@@ -1554,7 +1573,7 @@ Ako izaberete da ga unesete, ono će biti korišćeno za pripisivanje vašeg rad
 'right-edit' => 'uređivanje stranica',
 'right-createpage' => 'pravljenje stranica (izuzev stranica za razgovor)',
 'right-createtalk' => 'pravljenje stranica za razgovor',
-'right-createaccount' => 'pravljenje novih korisničkih naloga',
+'right-createaccount' => 'otvaranje novih korisničkih naloga',
 'right-minoredit' => 'označavanje izmena kao manje',
 'right-move' => 'premeštanje stranica',
 'right-move-subpages' => 'premeštanje stranica s njihovim podstranicama',
@@ -1574,6 +1593,7 @@ Ako izaberete da ga unesete, ono će biti korišćeno za pripisivanje vašeg rad
 'right-writeapi' => 'pisanje API-ja',
 'right-delete' => 'brisanje stranica',
 'right-bigdelete' => 'brisanje stranica s velikom istorijom',
+'right-deletelogentry' => 'Brisanje i vraćanje određenih stavki u dnevniku',
 'right-deleterevision' => 'brisanje i vraćanje određenih izmena stranica',
 'right-deletedhistory' => 'pregledanje obrisanih stavki istorije bez povezanog teksta',
 'right-deletedtext' => 'pregledanje obrisanog teksta i izmena između obrisanih izmena',
@@ -1582,7 +1602,7 @@ Ako izaberete da ga unesete, ono će biti korišćeno za pripisivanje vašeg rad
 'right-suppressrevision' => 'pregledanje i vraćanje izmena koje su sakrivene od strane administratora',
 'right-suppressionlog' => 'gledanje privatnih dnevnika',
 'right-block' => 'blokiranje daljih izmena drugih korisnika',
-'right-blockemail' => 'blokiranje korisnika da šalju e-poruke',
+'right-blockemail' => 'onemogućavanje korisnicima da šalju e-poruke',
 'right-hideuser' => 'blokiranje korisničkog imena i njegovo sakrivanje od javnosti',
 'right-ipblock-exempt' => 'zaobilaženje blokiranja IP adrese, samoblokiranja i blokiranja opsega',
 'right-proxyunbannable' => 'zaobilaženje samoblokiranja posrednika',
@@ -1622,7 +1642,7 @@ Ako izaberete da ga unesete, ono će biti korišćeno za pripisivanje vašeg rad
 'action-edit' => 'uređivanje ove stranice',
 'action-createpage' => 'pravljenje stranica',
 'action-createtalk' => 'pravljenje stranica za razgovor',
-'action-createaccount' => 'pravljenje ovog korisničkog naloga',
+'action-createaccount' => 'otvaranje ovog korisničkog naloga',
 'action-minoredit' => 'označavanje ove izmene kao manje',
 'action-move' => 'premeštanje ove stranice',
 'action-move-subpages' => 'premeštanje ove stranice i njenih podstranica',
@@ -1658,7 +1678,7 @@ Ako izaberete da ga unesete, ono će biti korišćeno za pripisivanje vašeg rad
 'nchanges' => '$1 {{PLURAL:$1|izmena|izmene|izmena}}',
 'recentchanges' => 'Skorašnje izmene',
 'recentchanges-legend' => 'Postavke skorašnjih izmena',
-'recentchanges-summary' => 'Ovde pratite najskorije izmene na vikiju.',
+'recentchanges-summary' => 'Pratite skorašnje izmene na ovoj stranici.',
 'recentchanges-feed-description' => 'Pratite skorašnje izmene uz pomoć ovog dovoda.',
 'recentchanges-label-newpage' => 'Nova stranica',
 'recentchanges-label-minor' => 'Manja izmena',
@@ -1876,14 +1896,15 @@ Ako se problem ne reši, kontaktirajte [[Special:ListUsers/sysop|administratora]
 'backend-fail-writetemp' => 'Ne mogu da pišem u privremenoj datoteci.',
 'backend-fail-closetemp' => 'Ne mogu da zatvorim privremenu datoteku.',
 'backend-fail-read' => 'Ne mogu da pročitam datoteku $1.',
-'backend-fail-create' => 'Ne mogu da napravim datoteku $1.',
-'backend-fail-maxsize' => 'Ne mogu da napravim datoteku $1 jer je veća od {{PLURAL:$2|$2 bajta|$2 bajta|$2 bajtova}}.',
+'backend-fail-create' => 'Ne mogu da zapišem datoteku $1.',
+'backend-fail-maxsize' => 'Ne mogu da zapišem datoteku $1 jer je veća od {{PLURAL:$2|$2 bajta|$2 bajta|$2 bajtova}}.',
 'backend-fail-readonly' => 'Skladišna osnova „$1“ trenutno ne može da se zapisuje. Navedeni razlog glasi: „$2“',
 'backend-fail-synced' => 'Datoteka „$1“ je nedosledna između unutrašnjih skladišnih osnova',
 'backend-fail-connect' => 'Ne mogu da se povežem sa skladišnom osnovom „$1“.',
 'backend-fail-internal' => 'Došlo je do nepoznate greške u skladišnoj osnovi „$1“.',
 'backend-fail-contenttype' => 'Ne mogu da utvrdim kakav sadržaj ima datoteka koju treba da smestim u „$1“.',
 'backend-fail-batchsize' => 'Skladišna osnova je dobila blokadu od $1 {{PLURAL:$1|operacije|operacije|operacija}}; ograničenje je $2 {{PLURAL:$2|operacija|operacije|operacija}}.',
+'backend-fail-usable' => 'Ne mogu da zapišem datoteku $1 jer nemate dovoljno dozvola ili vam nedostaju fascikle/sadržaoci.',
 
 # File journal errors
 'filejournal-fail-dbconnect' => 'Ne mogu da se povežem s novinarskom bazom za skladišnu osnovu „$1“.',
@@ -1898,6 +1919,7 @@ Ako se problem ne reši, kontaktirajte [[Special:ListUsers/sysop|administratora]
 'lockmanager-fail-releaselock' => 'Ne mogu da oslobodim katanac za „$1“.',
 'lockmanager-fail-db-bucket' => 'Ne mogu da kontaktiram s dovoljno katanaca u kanti $1.',
 'lockmanager-fail-db-release' => 'Ne mogu da oslobodim katance u bazi $1.',
+'lockmanager-fail-svr-acquire' => 'Ne mogu da dobijem katance na serveru $1.',
 'lockmanager-fail-svr-release' => 'Ne mogu da oslobodim katance na serveru $1.',
 
 # ZipDirectoryReader
@@ -1969,7 +1991,7 @@ Kad je poređano po korisniku, popis prikazuje samo one datoteke čije je posled
 'listfiles_search_for' => 'Naziv datoteke:',
 'imgfile' => 'datoteka',
 'listfiles' => 'Spisak datoteka',
-'listfiles_thumb' => 'Umanjeni prikaz',
+'listfiles_thumb' => 'Minijatura',
 'listfiles_date' => 'Datum',
 'listfiles_name' => 'Naziv',
 'listfiles_user' => 'Korisnik',
@@ -2006,8 +2028,8 @@ Dostupan je i [[Special:WhatLinksHere/$2|potpuni spisak]].',
 'sharedupload' => 'Ova datoteka se nalazi na $1 i može se koristiti i na drugim projektima.',
 'sharedupload-desc-there' => 'Ova datoteka se nalazi na $1 i može se koristiti i na drugim projektima.
 Pogledajte [$2 stranicu za opis datoteke] za više detalja o njoj.',
-'sharedupload-desc-here' => 'Ova datoteka se nalazi na $1 i može se koristiti i na drugim projektima.
-Opis na [$2 stranici datoteke] je prikazan ispod.',
+'sharedupload-desc-here' => 'Ova datoteka se nalazi na $1 i može da se koristi na drugim projektima.
+Opis njene [$2 stranice za opis] je prikazan ispod.',
 'sharedupload-desc-edit' => 'Ova datoteka se nalazi na $1 i može da se koristi na drugim projektima.
 Njen opis možete da izmenite na [$2 odgovarajućoj stranici].',
 'sharedupload-desc-create' => 'Ova datoteka se nalazi na $1 i može da se koristi na drugim projektima.
@@ -2034,7 +2056,7 @@ Njen opis možete da izmenite na [$2 odgovarajućoj stranici].',
 'filedelete' => 'Obriši $1',
 'filedelete-legend' => 'Obriši datoteku',
 'filedelete-intro' => "Brišete datoteku '''[[Media:$1|$1]]''' zajedno s njenom istorijom.",
-'filedelete-intro-old' => "Brišete izdanje datoteke '''[[Media:$1|$1]]''' od [$4 $2; $3].",
+'filedelete-intro-old' => "Brišete verziju datoteke '''[[Media:$1|$1]]''' od [$4 $2; $3].",
 'filedelete-comment' => 'Razlog:',
 'filedelete-submit' => 'Obriši',
 'filedelete-success' => "Datoteka '''$1''' je obrisana.",
@@ -2100,9 +2122,9 @@ Pre brisanja proverite da li druge stranice vode do tih šablona.',
 
 'disambiguations' => 'Stranice do višeznačnih odrednica',
 'disambiguationspage' => 'Template:Višeznačna odrednica',
-'disambiguations-text' => "Sledeće stranice su povezane s '''višeznačnom odrednicom'''.
-One bi trebalo biti upućene ka odgovarajućem članku.
-Stranica se smatra višeznačnom odrednicom ako koristi šablon koji je povezan sa spiskom [[MediaWiki:Disambiguationspage]].",
+'disambiguations-text' => "Sledeće stranice sadrže bar jednu vezu do '''višeznačne odrednice'''.
+Umesto toga, valjalo bi da vode do odgovarajuće teme.
+Stranica se smatra višeznačnom odrednicom ako koristi šablon koji vodi od [[MediaWiki:Disambiguationspage]].",
 
 'doubleredirects' => 'Dvostruka preusmerenja',
 'doubleredirectstext' => 'Ova stranica prikazuje stranice koje preusmeravaju na druga preusmerenja.
@@ -2210,6 +2232,7 @@ Druge veb stranice mogu koristiti sliku preko direktne adrese, tako da i pored t
 Možete suziti prikaz odabirući vrstu istorije, korisničkog imena ili tražene stranice.',
 'logempty' => 'Nema pronađenih stavki u istoriji.',
 'log-title-wildcard' => 'traži naslove koji počinju s ovim tekstom',
+'showhideselectedlogentries' => 'Prikaži/sakrij izabrane zapise',
 
 # Special:AllPages
 'allpages' => 'Sve stranice',
@@ -2433,7 +2456,7 @@ Potvrdite svoju nameru, da razumete posledice i da ovo radite u skladu s [[{{Med
 Pogledajte ''$2'' za više detalja.",
 'dellogpage' => 'Dnevnik brisanja',
 'dellogpagetext' => 'Ispod je spisak poslednjih brisanja.',
-'deletionlog' => 'Dnevnik brisanja',
+'deletionlog' => 'dnevnik brisanja',
 'reverted' => 'Vraćeno na raniju izmenu',
 'deletecomment' => 'Razlog:',
 'deleteotherreason' => 'Drugi/dodatni razlog:',
@@ -2452,6 +2475,8 @@ Njeno brisanje može poremetiti bazu podataka, stoga postupajte s oprezom.',
 'rollback' => 'Vrati izmene',
 'rollback_short' => 'Vrati',
 'rollbacklink' => 'vrati',
+'rollbacklinkcount' => 'vrati $1 {{PLURAL:$1|izmenu|izmene|izmena}}',
+'rollbacklinkcount-morethan' => 'vrati više od $1 {{PLURAL:$1|izmene|izmene|izmena}}',
 'rollbackfailed' => 'Neuspešno vraćanje',
 'cantrollback' => 'Ne mogu da vratim izmenu.
 Poslednji autor je ujedno i jedini.',
@@ -2627,7 +2652,7 @@ Izveštaj o blokiranim korisnicima se nalazi ispod:',
 'sp-contributions-submit' => 'Pretraži',
 
 # What links here
-'whatlinkshere' => 'Šta je povezano ovde',
+'whatlinkshere' => 'Šta vodi ovde',
 'whatlinkshere-title' => 'Stranice koje su povezane sa „$1“',
 'whatlinkshere-page' => 'Stranica:',
 'linkshere' => "Sledeće stranice imaju vezu do '''[[:$1]]''':",
@@ -2648,7 +2673,7 @@ Izveštaj o blokiranim korisnicima se nalazi ispod:',
 # Block/unblock
 'autoblockid' => 'Samoblokiranje #$1',
 'block' => 'Blokiraj korisnika',
-'unblock' => 'Odblokiraj korisnika',
+'unblock' => 'Deblokiraj korisnika',
 'blockip' => 'Blokiraj korisnika',
 'blockip-title' => 'Blokiranje korisnika',
 'blockip-legend' => 'Blokiraj korisnika',
@@ -2669,7 +2694,7 @@ Izaberite konkretan razlog ispod (primer: navođenje konkretnih stranica koje su
 ** Neprihvatljivo korisničko ime',
 'ipb-hardblock' => 'Zabrani prijavljenim korisnicima da uređuju s ove IP adrese',
 'ipbcreateaccount' => 'Onemogući otvaranje naloga',
-'ipbemailban' => 'Zabrani korisniku slanje e-poruka',
+'ipbemailban' => 'Onemogući korisniku da šalje e-poruke',
 'ipbenableautoblock' => 'Automatski blokiraj poslednju IP adresu ovog korisnika i sve daljnje adrese s kojih pokuša da uređuje',
 'ipbsubmit' => 'Blokiraj ovog korisnika',
 'ipbother' => 'Drugo vreme:',
@@ -2688,11 +2713,11 @@ Blokiranja možete da pogledate [[Special:BlockList|ovde]].',
 'ipb-blockingself' => 'Ovom radnjom ćete blokirati sebe! Jeste li sigurni da to želite?',
 'ipb-confirmhideuser' => 'Upravo ćete blokirati korisnika s uključenom mogućnošću „sakrij korisnika“. Ovim će korisničko ime biti sakriveno u svim spiskovima i izveštajima. Želite li to da uradite?',
 'ipb-edit-dropdown' => 'Uredi razloge blokiranja',
-'ipb-unblock-addr' => 'Odblokiraj $1',
-'ipb-unblock' => 'Odblokiraj korisničko ime ili IP adresu',
+'ipb-unblock-addr' => 'Deblokiraj $1',
+'ipb-unblock' => 'Deblokiraj korisničko ime ili IP adresu',
 'ipb-blocklist' => 'Pogledaj postojeća blokiranja',
 'ipb-blocklist-contribs' => 'Doprinosi za $1',
-'unblockip' => 'Odblokiraj korisnika',
+'unblockip' => 'Deblokiraj korisnika',
 'unblockiptext' => 'Koristite obrazac ispod da biste vratili pravo pisanja blokiranoj IP adresi ili korisničkom imenu.',
 'ipusubmit' => 'Ukloni ovu blokadu',
 'unblocked' => '[[User:$1|$1]] je deblokiran',
@@ -2724,7 +2749,7 @@ Blokiranja možete da pogledate [[Special:BlockList|ovde]].',
 'ipblocklist-empty' => 'Spisak blokiranja je prazan.',
 'ipblocklist-no-results' => 'Tražena IP adresa ili korisničko ime nije blokirano.',
 'blocklink' => 'blokiraj',
-'unblocklink' => 'odblokiraj',
+'unblocklink' => 'deblokiraj',
 'change-blocklink' => 'promeni blokiranje',
 'contribslink' => 'doprinosi',
 'emaillink' => 'pošalji e-poruku',
@@ -2740,7 +2765,7 @@ Istorija sakrivanja se nalazi ispod:',
 'blocklogtext' => 'Ovo je dnevnik blokiranja i deblokiranja korisnika.
 Automatski blokirane IP adrese nisu navedene.
 Tekuće zabrane i blokiranja možete naći [[Special:BlockList|ovde]].',
-'unblocklogentry' => '{{GENDER:|je odblokirao|je odblokirala|je odblokirao}} „$1“',
+'unblocklogentry' => '{{GENDER:|je deblokirao|je deblokirala|je deblokirao}} „$1“',
 'block-log-flags-anononly' => 'samo anonimni korisnici',
 'block-log-flags-nocreate' => 'onemogućeno otvaranje naloga',
 'block-log-flags-noautoblock' => 'automatsko blokiranje je onemogućeno',
@@ -3029,7 +3054,7 @@ Pokušajte ponovo.',
 'tooltip-ca-edit' => 'Možete da uređujete ovu stranicu. Koristite pretpregled pre snimanja',
 'tooltip-ca-addsection' => 'Započnite novi odeljak',
 'tooltip-ca-viewsource' => 'Ova stranica je zaključana. Možete da vidite izvorni kod.',
-'tooltip-ca-history' => 'Prethodna izdanja ove stranice',
+'tooltip-ca-history' => 'Prethodne verzije ove stranice',
 'tooltip-ca-protect' => 'Zaštitite ovu stranicu',
 'tooltip-ca-unprotect' => 'Promeni zaštitu ove stranice',
 'tooltip-ca-delete' => 'Obrišite ovu stranicu',
@@ -3078,7 +3103,7 @@ Pokušajte ponovo.',
 'tooltip-watchlistedit-raw-submit' => 'Ažuriraj spisak',
 'tooltip-recreate' => 'Ponovo napravite stranicu iako je obrisana',
 'tooltip-upload' => 'Započnite otpremanje',
-'tooltip-rollback' => '„Vrati“ poništava poslednju izmenu ove stranice s jednim klikom',
+'tooltip-rollback' => 'Opcija „Vrati“ vraća izmene poslednjeg korisnika',
 'tooltip-undo' => 'Vraća ovu izmenu i otvara obrazac za uređivanje.',
 'tooltip-preferences-save' => 'Sačuvaj postavke',
 'tooltip-summary' => 'Unesite kratak opis',
@@ -3140,7 +3165,8 @@ Ovo je verovatno izazvano vezom do spoljašnjeg sajta koji se nalazi na crnoj li
 'spamprotectionmatch' => 'Sledeći tekst je izazvao naš filter za nepoželjne poruke: $1',
 'spambot_username' => 'Čišćenje nepoželjnih poruka u Medijavikiji',
 'spam_reverting' => 'Vraćam na poslednju izmenu koja ne sadrži veze do $1',
-'spam_blanking' => 'Sve izmene koje sadrže veze do $1, brišem',
+'spam_blanking' => 'Sve izmene sadrže veze do $1. Čistim',
+'spam_deleting' => 'Sve izmene sadrže veze do $1. Brišem',
 
 # Info page
 'pageinfo-title' => 'Podaci o „$1“',
@@ -3179,7 +3205,7 @@ Ovo je verovatno izazvano vezom do spoljašnjeg sajta koji se nalazi na crnoj li
 
 # Patrol log
 'patrol-log-page' => 'Dnevnik patroliranja',
-'patrol-log-header' => 'Ovo je istorija pregledanih izmena.',
+'patrol-log-header' => 'Ovo je dnevnik patroliranih izmena.',
 'log-show-hide-patrol' => '$1 dnevnik patroliranja',
 
 # Image deletion
@@ -3478,7 +3504,7 @@ Ako je prvobitno stanje datoteke promenjeno, moguće je da neki detalji ne opisu
 'exif-datetimeexpires' => 'Ne koristi nakon',
 'exif-datetimereleased' => 'Objavljeno',
 'exif-originaltransmissionref' => 'Izvorni prenos kôda lokacije',
-'exif-identifier' => 'Oznaka',
+'exif-identifier' => 'Naznaka',
 'exif-lens' => 'Korišćeni objektiv',
 'exif-serialnumber' => 'Serijski broj kamere',
 'exif-cameraownername' => 'Vlasnik kamere',
@@ -3836,7 +3862,7 @@ Ovaj potvrdni kod ističe $6 u $7.',
 
 # Delete conflict
 'deletedwhileediting' => "'''Upozorenje''': ova stranica je obrisana nakon što ste počeli s uređivanjem!",
-'confirmrecreate' => "[[User:$1|$1]] ([[User talk:$1|razgovor]]) {{GENDER:$1|je obrisao|je obrisala|obrisa}} ovu stranicu nakon što ste počeli da je uređujete, sa sledećim razlogom:
+'confirmrecreate' => "[[User:$1|$1]] ([[User talk:$1|razgovor]]) {{GENDER:$1|je obrisao|je obrisala|je obrisao}} ovu stranicu nakon što ste počeli da je uređujete iz sledećeg razloga:
 : ''$2''
 Potvrdite da stvarno želite da napravite stranicu.",
 'confirmrecreate-noreason' => 'Korisnik [[User:$1|$1]] ([[User talk:$1|razgovor]]) je obrisao ovu stranicu nakon što ste počeli da ga uređujete. Potvrdite da stvarno želite da ponovo napravite ovu stranicu.',
@@ -4137,17 +4163,17 @@ Slike su prikazane u punoj veličini, a druge vrste datoteka se pokreću pomoću
 'sqlite-no-fts' => '$1 bez podrške pretrage celog teksta',
 
 # New logging system
-'logentry-delete-delete' => '$1 je obrisao stranicu $3',
-'logentry-delete-restore' => '$1 je vratio stranicu $3',
-'logentry-delete-event' => '$1 je promenio vidljivost {{PLURAL:$5|događaja u istoriji|$5 događaja u istoriji|$5 događaja u istoriji}} na $3: $4',
-'logentry-delete-revision' => '$1 je promenio vidljivost {{PLURAL:$5|izmene|$5 izmene|$5 izmena}} na stranici $3: $4',
-'logentry-delete-event-legacy' => '$1 je promenio vidljivost događajâ u istoriji na $3',
-'logentry-delete-revision-legacy' => '$1 je promenio vidljivost izmenâ na stranici $3',
-'logentry-suppress-delete' => '$1 je potisnuo stranicu $3',
-'logentry-suppress-event' => '$1 je potajno promenio vidljivost {{PLURAL:$5|događaja u istoriji|$5 događaja u istoriji|$5 događaja u istoriji}} na $3: $4',
-'logentry-suppress-revision' => '$1 je potajno promenio vidljivost {{PLURAL:$5|izmene|$5 izmene|$5 izmena}} na stranici $3: $4',
-'logentry-suppress-event-legacy' => '$1 je potajno promenio vidljivost događajâ u istoriji na $3',
-'logentry-suppress-revision-legacy' => '$1 je potajno promenio vidljivost izmenâ na stranici $3',
+'logentry-delete-delete' => '$1 {{GENDER:|je obrisao|je obrisala|je obrisao}} $3',
+'logentry-delete-restore' => '$1 {{GENDER:|je vratio|je vratila|je vratio}} stranicu $3',
+'logentry-delete-event' => '$1 {{GENDER:$2|je promenio|je promenila|je promenio}} vidljivost {{PLURAL:$5|događaja|$5 događaja|$5 događaja}} u dnevniku na $3: $4',
+'logentry-delete-revision' => '$1 {{GENDER:|je promenio|je promenila|je promenio}} vidljivost {{PLURAL:$5|izmene|$5 izmene|$5 izmena}} na stranici $3: $4',
+'logentry-delete-event-legacy' => '$1 {{GENDER:|je promenio|je promenila|je promenio}} vidljivost događajâ u dnevniku na $3',
+'logentry-delete-revision-legacy' => '$1 {{GENDER:|je promenio|je promenila|je promenio}} vidljivost izmenâ na stranici $3',
+'logentry-suppress-delete' => '$1 {{GENDER:|je potisnuo|je potisnula|je potisnuo}} stranicu $3',
+'logentry-suppress-event' => '$1 je potajno {{GENDER:|promenio|promenila|promenio}} vidljivost {{PLURAL:$5|događaja|$5 događaja|$5 događaja}} u dnevniku na $3: $4',
+'logentry-suppress-revision' => '$1 je potajno {{GENDER:|promenio|promenila|promenio}} vidljivost {{PLURAL:$5|izmene|$5 izmene|$5 izmena}} na stranici $3: $4',
+'logentry-suppress-event-legacy' => '$1 je potajno {{GENDER:|promenio|promenila|promenio}} vidljivost događajâ u dnevniku na $3',
+'logentry-suppress-revision-legacy' => '$1 je potajno {{GENDER:|promenio|promenila|promenio}} vidljivost izmenâ na stranici $3',
 'revdelete-content-hid' => 'sadržaj je sakriven',
 'revdelete-summary-hid' => 'opis izmene je sakriven',
 'revdelete-uname-hid' => 'korisničko ime je sakriveno',
@@ -4156,15 +4182,15 @@ Slike su prikazane u punoj veličini, a druge vrste datoteka se pokreću pomoću
 'revdelete-uname-unhid' => 'korisničko ime je otkriveno',
 'revdelete-restricted' => 'primenjena ograničenja za administratore',
 'revdelete-unrestricted' => 'uklonjena ograničenja za administratore',
-'logentry-move-move' => '$1 je premestio stranicu $3 na $4',
-'logentry-move-move-noredirect' => '$1 je premestio stranicu $3 na $4 bez ostavljanja preusmerenja',
-'logentry-move-move_redir' => '$1 je premestio stranicu $3 na $4 preko preusmerenja',
-'logentry-move-move_redir-noredirect' => '$1 je premestio stranicu $3 na $4 preko preusmerenja bez ostavljanja preusmerenja',
-'logentry-patrol-patrol' => '$1 je označio izmenu $4 stranice $3 kao pregledanu',
-'logentry-patrol-patrol-auto' => '$1 je samostalno označio izmenu $4 stranice $3 kao pregledanu',
-'logentry-newusers-newusers' => '$1 je otvorio korisnički nalog',
-'logentry-newusers-create' => '$1 je otvorio korisnički nalog',
-'logentry-newusers-create2' => '$1 je otvorio korisnički nalog $3',
+'logentry-move-move' => '$1 {{GENDER:|je premestio|je premestila|je premestio}} stranicu $3 na $4',
+'logentry-move-move-noredirect' => '$1 {{GENDER:|je premestio|je premestila|je premestio}} stranicu $3 na $4 bez ostavljanja preusmerenja',
+'logentry-move-move_redir' => '$1 {{GENDER:|je premestio|je premestila|je premestio}} stranicu $3 na $4 preko preusmerenja',
+'logentry-move-move_redir-noredirect' => '$1 {{GENDER:|je premestio|je premestila|je premestio}} stranicu $3 na $4 preko preusmerenja bez ostavljanja preusmerenja',
+'logentry-patrol-patrol' => '$1 {{GENDER:|je označio|je označila|je označio}} izmenu $4 stranice $3 kao patroliranu',
+'logentry-patrol-patrol-auto' => '$1 je samostalno {{GENDER:|označio|označila|označio}} izmenu $4 stranice $3 kao pregledanu',
+'logentry-newusers-newusers' => '$1 {{GENDER:|je otvorio|je otvorila|je otvorio}} korisnički nalog',
+'logentry-newusers-create' => '$1 {{GENDER:|je otvorio|je otvorila|je otvorio}} korisnički nalog',
+'logentry-newusers-create2' => '$1 {{GENDER:|je otvorio|je otvorila|je otvorio}} korisnički nalog $3',
 'logentry-newusers-autocreate' => 'Nalog $1 je samostalno otvoren',
 'newuserlog-byemail' => 'lozinka je poslata e-poštom',
 
@@ -4195,6 +4221,8 @@ U suprotnom, poslužite se jednostavnim obrascem ispod. Vaš komentar će stajat
 'api-error-empty-file' => 'Poslata datoteka je prazna.',
 'api-error-emptypage' => 'Stvaranje novih praznih stranica nije dozvoljeno.',
 'api-error-fetchfileerror' => 'Unutrašnja greška: došlo je do greške pri dobavljanju datoteke.',
+'api-error-fileexists-forbidden' => 'Već postoji datoteka s imenom „$1“ i ne može da se zameni.',
+'api-error-fileexists-shared-forbidden' => 'Već postoji datoteka s imenom „$1“ u zajedničkoj riznici i ne može da se zameni.',
 'api-error-file-too-large' => 'Poslata datoteka je prevelika.',
 'api-error-filename-tooshort' => 'Naziv datoteke je prekratak.',
 'api-error-filetype-banned' => 'Ova vrsta datoteke je zabranjena.',
@@ -4234,5 +4262,5 @@ U suprotnom, poslužite se jednostavnim obrascem ispod. Vaš komentar će stajat
 'duration-millennia' => '$1 {{PLURAL:$1|milenijum|milenijuma|milenijuma}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|je zabranjena vrsta datoteke|su zabranjene vrste datoteka}}. {{PLURAL:$3|Dozvoljena vrsta datoteke je|Dozvoljene vrste datoteka su}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|je zabranjena vrsta datoteke|su zabranjene vrste datoteka}}. {{PLURAL:$3|Dozvoljena je|Dozvoljene su}} $2.',
 );
index 95a768f..13827b3 100644 (file)
@@ -136,7 +136,7 @@ $messages = array(
 'tog-hidepatrolled' => 'Sumputkeun anu geus diroris ti béréndélan nu anyar robah',
 'tog-newpageshidepatrolled' => 'Sumputkeun nu geus diroris tina béréndélan kaca anyar',
 'tog-extendwatchlist' => 'Legaan béréndélan ngarah sakabéh parobahanana kaawaskeun',
-'tog-usenewrc' => 'Nu anyar robah dina wanda séjén (maké JavaScript)',
+'tog-usenewrc' => 'Parobahan grup dumasar kaca dina béréndélan anyar robah jeung awaskeuneun (maké JavaScript)',
 'tog-numberheadings' => 'Nomeran lulugu sacara otomatis',
 'tog-showtoolbar' => "Témbongkeun ''toolbar'' édit (JavaScript)",
 'tog-editondblclick' => 'Édit kaca ku klik ganda (JavaScript)',
@@ -144,17 +144,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Fungsikeun ngédit sub-bagean kalawan klik-katuhu dina judul bagean (JavaScript)',
 'tog-showtoc' => 'Témbongkeun daptar eusi<br />(pikeun kaca nu leuwih ti tilu subjudul)',
 'tog-rememberpassword' => 'Apalkeun login kuring dina ieu panyungsi (pikeun paling lila $1 {{PLURAL:$1|poé|poé}})',
-'tog-watchcreations' => 'Awaskeun kaca jieunan kuring',
-'tog-watchdefault' => 'Tambahkeun kaca nu diédit ku anjeun kana awaskeuneun anjeun',
-'tog-watchmoves' => 'Awaskeun kaca nu dipindahkeun ku kuring',
-'tog-watchdeletion' => 'Awaskeun kaca nu dihapus ku kuring',
+'tog-watchcreations' => 'Tambahkeun kaca-kaca jieunan kuring jeung berkas muatan kuring kana awaskeuneun',
+'tog-watchdefault' => 'Tambahkeun kaca jeung berkas anu diédit ku kuring kana awaskeuneun',
+'tog-watchmoves' => 'Tambahkeun kaca jeung berkas anu dipindahkeun ka awaskeuneun',
+'tog-watchdeletion' => 'Tambahkeun kaca jeung berkas anu dihapus kana awaskeuneun',
 'tog-minordefault' => 'Tandaan sadaya éditan salaku minor luyu jeung ti dituna',
 'tog-previewontop' => 'Témbongkeun sawangan méméh kotak édit (lain sanggeusna)',
 'tog-previewonfirst' => 'Témbongkeun sawangan dina éditan munggaran',
 'tog-nocache' => "Tumpurkeun ''cache'' kaca dina pangaprak",
-'tog-enotifwatchlistpages' => 'Surélékan mun robah',
+'tog-enotifwatchlistpages' => 'Lamun aya kaca atawa berkas anu diawaskeun robah, béjaan ngaliwatan surélék',
 'tog-enotifusertalkpages' => 'Mun kaca obrolan kuring robah, béjaan ngaliwatan surélék',
-'tog-enotifminoredits' => 'Béjaan ogé (ngaliwatan surélék) mun aya parobahan leutik dina kacana',
+'tog-enotifminoredits' => 'Béjaan ogé (ngaliwatan surélék) lamun aya parobahan leutik dina kaca jeung berkasna',
 'tog-enotifrevealaddr' => 'Témbongkeun alamat surélék kuring dina surat émbaran',
 'tog-shownumberswatching' => 'Témbongkeun jumlah nu ngawaskeun',
 'tog-oldsig' => 'Paraf nu geus aya:',
@@ -2786,6 +2786,12 @@ Nu séjénna bakal disumputkeun sakumaha asalna.
 'exif-gpslongitude-e' => 'Gurat Wétan',
 'exif-gpslongitude-w' => 'Gurat Kulon',
 
+# Pseudotags used for GPSAltitudeRef
+'exif-gpsaltitude-above-sealevel' => '$1 {{PLURAL:$1|méter|méter}} luhureun beungeut laut',
+'exif-gpsaltitude-below-sealevel' => '$1 {{PLURAL:$1|méter|méter}} handapeun beungeut laut',
+
+'exif-gpsstatus-a' => 'Keur ngukur',
+
 'exif-gpsmeasuremode-2' => 'Ukuran 2-diménsi',
 'exif-gpsmeasuremode-3' => 'Ukuran 3-diménsi',
 
@@ -2794,9 +2800,53 @@ Nu séjénna bakal disumputkeun sakumaha asalna.
 'exif-gpsspeed-m' => 'Mil per jam',
 'exif-gpsspeed-n' => 'Knot',
 
+# Pseudotags used for GPSDestDistanceRef
+'exif-gpsdestdistance-k' => 'Kilométer',
+'exif-gpsdestdistance-m' => 'Mil',
+'exif-gpsdestdistance-n' => 'Mil laut',
+
+'exif-gpsdop-excellent' => 'Sampurna ($1)',
+'exif-gpsdop-good' => 'Alus ($1)',
+'exif-gpsdop-moderate' => 'Moderat ($1)',
+'exif-gpsdop-fair' => 'Cukup ($1)',
+'exif-gpsdop-poor' => 'Awon ($1)',
+
+'exif-objectcycle-a' => 'Isuk-isuk wungkul',
+'exif-objectcycle-p' => 'Soré wungkul',
+'exif-objectcycle-b' => 'Isuk jeung beurang',
+
 # Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef
+'exif-gpsdirection-t' => 'Arah sajati',
 'exif-gpsdirection-m' => 'Arah magnétik',
 
+'exif-ycbcrpositioning-1' => 'Nengah',
+
+'exif-dc-contributor' => 'Kontributor',
+'exif-dc-date' => 'Titimangsa',
+'exif-dc-publisher' => 'Pamedal',
+'exif-dc-relation' => 'Média anu tumali',
+'exif-dc-rights' => 'Hak',
+'exif-dc-source' => 'Média sumber',
+'exif-dc-type' => 'Jenis média',
+
+'exif-rating-rejected' => 'Ditolak',
+
+'exif-isospeedratings-overflow' => 'Leuwih ti 65535',
+
+'exif-iimcategory-ace' => 'Seni, budaya, jeung hiburan',
+'exif-iimcategory-clj' => 'Hukum jeung kajahatan',
+'exif-iimcategory-dis' => 'Bencana jeung kacilakaan',
+'exif-iimcategory-edu' => 'Atikan',
+'exif-iimcategory-evn' => 'Lingkungan',
+'exif-iimcategory-hth' => 'Kawaluyaan',
+'exif-iimcategory-pol' => 'Politik',
+'exif-iimcategory-rel' => 'Ageman jeung kayakinan',
+'exif-iimcategory-soi' => 'Isu sosial',
+'exif-iimcategory-spo' => 'Olahraga',
+'exif-iimcategory-wea' => 'Cuaca',
+
+'exif-urgency-normal' => 'Normal ($1)',
+
 # External editor support
 'edit-externally' => 'Édit koropak ieu migunakeun aplikasi éksternal',
 'edit-externally-help' => 'Baca [//www.mediawiki.org/wiki/Manual:External_editors pituduh ngatur] pikeun émbaran leuwih jéntré.',
@@ -3010,6 +3060,11 @@ Coba ku sawangan normal.',
 'revdelete-unrestricted' => 'Watesan akses kuncén dihapuskeun',
 'newuserlog-byemail' => 'Sandi geus dikirim maké surélék.',
 
+# Feedback
+'feedback-subject' => 'Ngeunaan:',
+'feedback-message' => 'Surat:',
+'feedback-cancel' => 'Bolay',
+
 # API errors
 'api-error-file-too-large' => 'Berkas nu dikirim gedé teuing.',
 'api-error-filename-tooshort' => 'Ngaran berkas pondok teuing.',
@@ -3018,6 +3073,17 @@ Coba ku sawangan normal.',
 'api-error-illegal-filename' => 'Ngaran berkas kitu dipahing.',
 'api-error-mustbeloggedin' => 'Anjeun kudu asup log pikeun ngunggahkeun berkas.',
 
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|detik|detik}}',
+'duration-minutes' => '$1 {{PLURAL:$1|menit|menit}}',
+'duration-hours' => '$1 {{PLURAL:$1|jam|jam}}',
+'duration-days' => '$1 {{PLURAL:$1|poé|poé}}',
+'duration-weeks' => '$1 {{PLURAL:$1|minggu|minggu}}',
+'duration-years' => '$1 {{PLURAL:$1|taun|taun}}',
+'duration-decades' => '$1 {{PLURAL:$1|dékadeu|dékadeu}}',
+'duration-centuries' => '$1 {{PLURAL:$1|abad|abad}}',
+'duration-millennia' => '$1 {{PLURAL:$1|milénium|milénium}}',
+
 # Unknown messages
 'api-error-filetype-banned-type' => '$1 kaasup tipeu koropak nu teu dicaram. {{PLURAL:$3|Nu diwidian nyaéta|Nu diwidian nyaéta}} $2.',
 );
index 27a0b20..a852976 100644 (file)
@@ -12,6 +12,7 @@
  * @author Dafer45
  * @author Diupwijk
  * @author EPO
+ * @author Fader
  * @author Fluff
  * @author GameOn
  * @author Greggegorius
@@ -582,6 +583,10 @@ $1',
 'youhavenewmessages' => 'Du har $1 ($2).',
 'newmessageslink' => 'nya meddelanden',
 'newmessagesdifflink' => 'senaste ändringen',
+'youhavenewmessagesfromusers' => 'Du har $1 från {{PLURAL:$3|en annan användare|$3 användare}} ($2).',
+'youhavenewmessagesmanyusers' => 'Du har $1 från många användare ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|ett nytt meddelande|nya meddelanden}}',
+'newmessagesdifflinkplural' => 'senaste {{PLURAL:$1|ändring|ändringar}}',
 'youhavenewmessagesmulti' => 'Du har nya meddelanden på $1',
 'editsection' => 'redigera',
 'editold' => 'redigera',
@@ -730,6 +735,7 @@ Glöm inte att justera dina [[Special:Preferences|{{SITENAME}}-inställningar]].
 'remembermypassword' => 'Spara min inloggning på den här datorn (i max $1 {{PLURAL:$1|dygn|dygn}})',
 'securelogin-stick-https' => 'Fortsätt vara ansluten till HTTPS efter inloggning',
 'yourdomainname' => 'Din domän',
+'password-change-forbidden' => 'Du kan inte ändra lösenord på denna wiki.',
 'externaldberror' => 'Antingen inträffade autentiseringsproblem med en extern databas, eller så får du inte uppdatera ditt externa konto.',
 'login' => 'Logga in',
 'nav-login-createaccount' => 'Logga in / skapa konto',
@@ -968,6 +974,10 @@ Du kan [[Special:Search/{{PAGENAME}}|söka efter denna sidtitel]] på andra sido
 'noarticletext-nopermission' => 'Det finns för tillfället ingen text på denna sida.
 Du kan [[Special:Search/{{PAGENAME}}|söka efter denna sidas titel]] i andra sidor,
 eller <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söka i relevanta loggar]</span>.',
+'missing-revision' => 'Revisionen #$1 av sidan med namnet "{{PAGENAME}}" finns inte.
+
+Detta orsakas vanligen av efter en gammal historiklänk till en sida som har raderats.
+Detaljer 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.',
 'userpage-userdoesnotexist-view' => 'Kontot "$1" är inte registrerat.',
 'blocked-notice-logextract' => 'Användaren är blockerad.
@@ -1095,6 +1105,7 @@ Dessa parametrar har uteslutits.',
 'expansion-depth-exceeded-warning' => 'Sidan överskrider expansionsdjupet',
 'parser-unstrip-loop-warning' => 'Tagavskalningsloop upptäcktes',
 'parser-unstrip-recursion-limit' => 'Tagavskalningsloop överskred rekursionsgränsen ($1)',
+'converter-manual-rule-error' => 'Fel upptäcktes i manuell språkkonverteringsregel',
 
 # "Undo" feature
 'undo-success' => 'Redigeringen kan göras ogjord.
@@ -3999,5 +4010,5 @@ Annars kan du använda det enkla formuläret nedan. Din kommentar kommer att lä
 'duration-millennia' => '$1 {{PLURAL:$1|millennium|millennier}}',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1 är inte {{PLURAL:$4|en tillåten filtyp|tillåtna filtyper}}. {{PLURAL:$3|Tillåtna filtyper|Tillåten filtyp}} är $2.',
+'api-error-filetype-banned-type' => '$1 är inte {{PLURAL:$4|en tillåten filtyp|tillåtna filtyper}}. {{PLURAL:$3|Tillåten filtyp|Tillåtna filtyper}} är $2.',
 );
index 61b24d0..90a5ce5 100644 (file)
@@ -139,17 +139,17 @@ $messages = array(
 'tog-editsectiononrightclick' => 'Wezesha sehemu ya kuandikia kwa kubonyeza kitufe cha kulia cha puku yako juu ya sehemu ya majina husika (JavaScript)',
 'tog-showtoc' => 'Onyesha mistari ya yaliyomo (kwa kila kurasa iliyo na zaidi ya vichwa vya habari 3)',
 'tog-rememberpassword' => 'Kumbuka kuingia kwangu pamoja na neno la siri katika kivinjari hiki (kwa muda usiozidi {{PLURAL:$1|siku}} $1)',
-'tog-watchcreations' => 'Weka kurasa nilizoumba katika maangalizi yangu',
-'tog-watchdefault' => 'Weka kurasa zote nilizohariri katika maangalizi yangu',
-'tog-watchmoves' => 'Weka kurasa zote nilizohamisha katika maangalizi yangu',
-'tog-watchdeletion' => 'Weka kurasa zote nilizofuta katika maangalizi yangu',
+'tog-watchcreations' => 'Ongeza kurasa nilizoumba katika maangalizi yangu',
+'tog-watchdefault' => 'Ongeza kurasa zote nilizohariri katika maangalizi yangu',
+'tog-watchmoves' => 'Ongeza kurasa zote nilizohamisha katika maangalizi yangu',
+'tog-watchdeletion' => 'Ongeza kurasa zote nilizofuta katika maangalizi yangu',
 'tog-minordefault' => 'Weka alama zote za mabadiliko madogo kama matumizi mbadala',
 'tog-previewontop' => 'Onyesha mandhari kabla ya sanduku la kuhariria',
 'tog-previewonfirst' => 'Onyesha mandhari unapoanza kuhariri',
 'tog-nocache' => 'Kurasa zisiwekwe katika kache (akiba ya muda) ya kivinjari',
-'tog-enotifwatchlistpages' => 'Nitumie barua pepe pale kurasa zilizopo katika maangalizi yangu zikibadilishwa',
+'tog-enotifwatchlistpages' => 'Nitumie barua pepe pale kurasa zilizopo katika maangalizi yangu zinabadilishwa',
 'tog-enotifusertalkpages' => 'Nitumie barua pepe pale ukurasa wangu wa majadiliano ukiwa na mabadiliko',
-'tog-enotifminoredits' => 'Pia nitumie barua pale mabadiliko ya ukurasa yanapokuwa madogo tu',
+'tog-enotifminoredits' => 'Pia nitumie barua pale mabadiliko ya ukurasa yanapokuwa madogo tu.',
 'tog-enotifrevealaddr' => 'Onyesha anwani ya barua pepe yangu katika barua pepe za taarifa',
 'tog-shownumberswatching' => 'Onyesha idadi ya watumiaji waangalizi',
 'tog-oldsig' => 'Sahihi iliyopo:',
@@ -390,6 +390,10 @@ Tazama [[Special:Version|ukurasa wa toleo]].',
 'youhavenewmessages' => 'Una $1 ($2).',
 'newmessageslink' => 'ujumbe mpya',
 'newmessagesdifflink' => 'badiliko la mwisho',
+'youhavenewmessagesfromusers' => 'Una $1 kutoka {{PLURAL:$3|another user|$3 users}} ($2)',
+'youhavenewmessagesmanyusers' => 'Una $1 kutoka kwa watumiaji wengi $2',
+'newmessageslinkplural' => '{{PLURAL:$1|a new message|ujumbe mpya}}',
+'newmessagesdifflinkplural' => 'last {{PLURAL:$1|change|mabadiliko}}',
 'youhavenewmessagesmulti' => 'Umepokea jumbe mpya kule $1',
 'editsection' => 'hariri',
 'editold' => 'hariri',
@@ -513,6 +517,8 @@ $2',
 'ns-specialprotected' => 'Kurasa maalumu haziwezi kuhaririwa.',
 'titleprotected' => 'Jina hili limekingwa lisiumbwe na [[User:$1|$1]].
 Sababu zilizotolewa ni "\'\'$2\'\'".',
+'exception-nologin' => 'Hujaingia',
+'exception-nologin-text' => 'Ukurasa huu unahitaji kuwa mtumiaji awe ameingia katika wiki hii.',
 
 # Virus scanner
 'virus-badscanner' => "Usanidi mbaya: kiskani virusi hakijulikani: ''$1''",
@@ -532,6 +538,7 @@ Usisahau kubadilisha mapendekezo yako ya [[Special:Preferences|{{SITENAME}}]].',
 'remembermypassword' => 'Kumbuka kuingia kwangu katika kivinjari hiki (kwa muda usiozidi {{PLURAL:$1|siku}} $1)',
 'securelogin-stick-https' => 'Endelea kuunganishwa na HTTPS baada ya kuingia',
 'yourdomainname' => 'Tovuti yako:',
+'password-change-forbidden' => 'Hauwezi kubadili nywila katika Wiki hii.',
 'externaldberror' => 'Huenda kulikuwa na hitilafu ya database au labda hauruhusiwi kubadilisha akaunti yako ya nje.',
 'login' => 'Ingia',
 'nav-login-createaccount' => 'Ingia/ sajili akaunti',
@@ -801,6 +808,7 @@ Kumbuka kwamba desturi ya kurasa za .css na .js hutumia herufi ndogo, yaani, {{n
 'note' => "'''Taarifa:'''",
 'previewnote' => "'''Hii ni hakikisho tu.''' 
 Mabadiliko hayajahifadhiwa bado!",
+'continue-editing' => 'Endelea kuhariri',
 'previewconflict' => 'Hakikisho hii inaonyesha maandiko yaliyopo sanduku la juu yataonekayo ukiyahifadhi.',
 'session_fail_preview' => "'''Pole! Hatukuweza kuhifadhi sahihisho lako kwa sababu data za kipindi zilipotelewa.'''
 Tafadhali jaribu tena.
@@ -879,6 +887,7 @@ Inaonekana kwamba ukurasa umefutwa.',
 'edit-no-change' => 'Uhariri wako haukufanikiwa, kwa sababu hapakuwa na mabadiliko yoyote kwenye maandishi.',
 'edit-already-exists' => 'Haikufanikiwa kuanzisha ukurasa mpya.
 Ukurasa wa jina hilo unapatikana tayari.',
+'defaultmessagetext' => 'Ujumbe uliopo',
 
 # Parser/template warnings
 'post-expand-template-inclusion-warning' => "'''Ilani:''' Kigezo kinajumlisha ukubwa uliozidi mno.
@@ -949,6 +958,14 @@ Bado unaweza [$1 kutazama sahihisho hili] iwapo utapenda kuendelea.",
 Unaweza kulitazama; maelezo mengine yapo kwenye [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} kumbukumbu ya ufutaji].",
 'rev-suppressed-text-view' => "Pitio la ukurasa huu '''limefichwa'''.
 Unaweza kulitazama; maelezo mengine yapo kwenye [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} kumbukumbu ya ufichaji].",
+'rev-deleted-unhide-diff' => "Pitio la ukurasa huu '''limefutwa'''.
+Maelezo yanaweza kupatikana kwenye [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} kumbukumbu ya ufutaji].
+Bado unaweza [$1 kutazama sahihisho hili] iwapo utapenda kuendelea.",
+'rev-suppressed-unhide-diff' => "Pitio la ukurasa huu '''limefutwa'''.
+Maelezo yanaweza kupatikana kwenye [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} kumbukumbu ya ufutaji].
+Bado unaweza [$1 kutazama sahihisho hili] iwapo utapenda kuendelea.",
+'rev-deleted-diff-view' => "Pitio hilo la ukurasa huu '''limefutwa'''.
+Unaweza masasisho; maelezo mengine yapo kwenye [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} kumbukumbu ya ufutaji]",
 'rev-delundel' => 'onyesha/ficha',
 'rev-showdeleted' => 'onyesha',
 'revisiondelete' => 'Kufuta/kurudisha mapitio',
@@ -1032,6 +1049,8 @@ Tazama [[Special:BlockList|IP block orodha ya uzuio wa IP]] kuona orodha ya zuio
 
 # Diffs
 'history-title' => 'Historia ya mapitio ya "$1"',
+'difference-title' => 'Tofauti kati ya marekesbisho "$1"',
+'difference-title-multipage' => 'Tofauti kati ya kurasa "$1" na "$2"',
 'difference-multipage' => '(Tofauti kati ya kurasa)',
 'lineno' => 'Mstari $1:',
 'compareselectedversions' => 'Linganisha mapitio mawili uliyochagua',
@@ -1126,6 +1145,7 @@ Ujue lakini kwamba kumbukumbu za {{SITENAME}} kule Google labda zilipitwa na wak
 'datedefault' => 'Chaguo-msingi',
 'prefs-beta' => 'Zana za Beta',
 'prefs-datetime' => 'Tarehe na saa',
+'prefs-labs' => 'Maumbile ya maabara',
 'prefs-user-pages' => 'Kurasa za watumiaji',
 'prefs-personal' => 'Kuhusu mtumiaji',
 'prefs-rc' => 'Mabadiliko ya karibuni',
@@ -1366,6 +1386,7 @@ Taarifa hii itakuwa wazi.',
 'action-userrights' => 'kubadilisha wezo zote za watumiaji',
 'action-userrights-interwiki' => 'kuhariri wezo za watumiaji kwenye wiki zingine',
 'action-siteadmin' => 'kufunga na kufungua hifadhidata',
+'action-sendemail' => 'tuma barua pepe',
 
 # Recent changes
 'nchanges' => '{{PLURAL:$1|badiliko|mabadiliko}} $1',
@@ -1400,6 +1421,7 @@ Taarifa hii itakuwa wazi.',
 'newsectionsummary' => '/* $1 */ mjadala mpya',
 'rc-enhanced-expand' => 'Onyesha maelezo mengine (inahitaji JavaScript)',
 'rc-enhanced-hide' => 'Ficha maelezo mengine',
+'rc-old-title' => 'ilitengenezwa hapo awali na "$1"',
 
 # Recent changes linked
 'recentchangeslinked' => 'Mabadiliko husika',
@@ -1551,7 +1573,18 @@ Tatizo likiendelea, uwasiliane na [[Special:ListUsers/sysop|mkabidhi]].',
 'upload-http-error' => 'Imetokea hitilafu ya HTTP: $1',
 
 # File backend
+'backend-fail-stream' => 'Haikuweza kutafuta faili "$1".',
+'backend-fail-notexists' => 'faili $1 haipo',
+'backend-fail-delete' => 'Haikuweza kufuta faili "$1".',
 'backend-fail-alreadyexists' => 'Faili $1 linapatikana tayari.',
+'backend-fail-store' => 'Haikuweza kunakili faili "$1" kwa "$2".',
+'backend-fail-copy' => 'Haikuweza kunakili faili "$1" kwa "$2".',
+'backend-fail-move' => 'Haikuweza kuhamisha faili "$1" kwa "$2".',
+'backend-fail-opentemp' => 'Haikuweza kuanzisha faili ya muda.',
+'backend-fail-writetemp' => 'Haikuweza kuandika kwa faili ya muda.',
+'backend-fail-closetemp' => 'Haikuweza kufunga faili ya muda.',
+'backend-fail-read' => 'Haikuweza kusoma faili "$1".',
+'backend-fail-create' => 'Haikuweza kuandika faili "$1".',
 
 # ZipDirectoryReader
 'zip-file-open-error' => 'Ilitokea hitilafu wakati wa kufungua faili kwa ajili ya ukaguzi wa ZIP.',
@@ -1729,9 +1762,9 @@ Kumbuka kuhakikisha kwamba hakuna viungo vingine vinavyoelekea kigezo fulani kab
 
 'disambiguations' => 'Kurasa zinazoungana na kurasa za uanishaji',
 'disambiguationspage' => 'Template:Maana',
-'disambiguations-text' => "Kurasa zinazofuata zina viungo vinavyoelekea '''kurasa ya kutofautishana maana'''.
-Ni afadhali kiungo kiende makala inayostahili moja kwa moja.<br />
-Kurasa za kutofautishana maana ni zile zinazotumia kigezo kinachoorodheshwa katika ukurasa wa [[MediaWiki:Disambiguationspage]].",
+'disambiguations-text' => "Kurasa zinazofuata zina angalau kiungo kimoja kinachoelekea kwa '''kurasa ya kutofautishana maana'''.
+Ni afadhali kiungo kiende makala yanayostahili moja kwa moja.<br />
+Ukurasa unatibiwa kama ukurasa wa kutofautishana maana inazotumia kigezo kinachoorodheshwa katika ukurasa wa [[MediaWiki:Disambiguationspage]].",
 
 'doubleredirects' => 'Maelekezo mawilimawili',
 'doubleredirectstext' => 'Ukurasa huu unaorodhesha kurasa zinazoelekeza kurasa zingine za kuelekeza.
@@ -1783,6 +1816,7 @@ Sasa unaelekeza kwa [[$2]].',
 'mostimages' => 'Mafaili yanayoungwa kuliko yote',
 'mostrevisions' => 'Kurasa zenye mapitio mengi kuliko zote',
 'prefixindex' => 'Kurasa zote zenye viambishi awali',
+'prefixindex-namespace' => 'Kurasa zote zenye kiambishi awali ($1)',
 'shortpages' => 'Kurasa fupi',
 'longpages' => 'Kurasa ndefu',
 'deadendpages' => 'Kurasa ambazo haziungi na ukurasa mwingine wowote',
@@ -1845,6 +1879,9 @@ Inaweza kuwa na herufi isiyoweza kutumiwa ndani ya majina ya kurasa.',
 'allpages-bad-ns' => 'Eneo la "$1" halipatikani kwenye {{SITENAME}}.',
 'allpages-hide-redirects' => 'Ficha kurasa za kuelekeza',
 
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'Tazama ya hivi karibuni.',
+
 # Special:Categories
 'categories' => 'Jamii',
 'categoriespagetext' => 'Jamii {{PLURAL:$1|inayofuata ina|zinazofuata zina}} kurasa au mafaili ya picha au sauti.
@@ -1939,6 +1976,10 @@ Anwani yako ya barua pepe ulioitaja katika [[Special:Preferences|mapendekezo yak
 'emailsenttext' => 'Barua pepe yako imetumwa.',
 'emailuserfooter' => 'Barua pepe hii imetumwa na $1 kwa $2 kwa kutumia zana ya "Kumtumia mtumiaji barua pepe" iliyopo {{SITENAME}}.',
 
+# User Messenger
+'usermessage-summary' => 'Inawacha ujumbe wa mfumo.',
+'usermessage-editor' => 'Jumbe za mfumo',
+
 # Watchlist
 'watchlist' => 'Maangalizi yangu',
 'mywatchlist' => 'Maangalizi yangu',
@@ -1967,26 +2008,26 @@ Ukitaka kufuta ukurasa huo kutoka maangalizi yako baadaye, bonyeza \"Acha kufuat
 'wlheader-enotif' => '* Huduma ya kuarifu kwa barua pepe imewezeshwa.',
 'wlheader-showupdated' => "* Kurasa zilizobadilika tangu ulivyotembelea mara ya mwisho zinaonyeshwa katika hali ya '''kukooza'''",
 'watchmethod-recent' => 'kupitia madabiliko ya karibuni ili kupata kurasa za maangalizi',
-'watchmethod-list' => 'kupitia kurasa za maangalizi ili kupata madabiliko ya karibuni',
-'watchlistcontains' => 'Orodha ya maangalizi yako ina {{PLURAL:$1|kitu|vitu}} $1.',
-'iteminvalidname' => "Kitu '$1' kina tatizo la jina batili...",
-'wlnote' => "{{PLURAL:$1|Badiliko la|Mabadiliko '''$1''' ya}} mwisho katika {{PLURAL:$2|saa iliyopita linaonyeshwa|masaa '''$2''' yaliyopita yanaonyeshwa}} chini, ilivyokuwa saa $4, tarehe $3.",
+'watchmethod-list' => 'Kupitia kurasa za maangalizi ili kupata madabiliko ya karibuni',
+'watchlistcontains' => 'Orodha ya maangalizi yako ina {{PLURAL:$1|pages|kurasa}}.',
+'iteminvalidname' => "Shida na kitu  '$1' ,  jina batili...",
+'wlnote' => "{{PLURAL:$1|is the last change|Mabadiliko '''$1''' ya}} mwisho katika {{PLURAL:$2|hour|masaa '''$2''' yaliyopita yanaonyeshwa}} chini, ilivyokuwa saa $4, tarehe $3.",
 'wlshowlast' => 'Onyesha kutoka masaa $1 siku $2 $3',
 'watchlist-options' => 'Hitiari za maangalizi',
 
 # Displayed when you click the "watch" button and it is in the process of watching
 'watching' => 'Unafuatilia...',
 'unwatching' => 'Umeacha kufuatilia...',
-'watcherrortext' => 'Hitilafu ilitokea ulipojaribu kubadilisha vipimo vya maangalizi yako kwa ajili ya "$1".',
+'watcherrortext' => 'Hitilafu ilitokea ulipojaribu kubadilisha mpangilio wa maangalizi yako ya "$1".',
 
 'enotif_mailer' => 'Huduma ya taarifa ya barua pepe kutoka kwa {{SITENAME}}',
-'enotif_reset' => 'Weka alama ya kutembelewa kwenye kurasa zote',
+'enotif_reset' => 'Weka alama kwa kurasa zote zilizotembelewa',
 'enotif_newpagetext' => 'Ukurasa huu ni mpya.',
 'enotif_impersonal_salutation' => 'Kwa mtumiaji wa {{SITENAME}}',
 'changed' => 'alibadilisha',
 'created' => 'alianzisha',
 'enotif_subject' => '$PAGEEDITOR $CHANGEDORCREATED ukurasa wa $PAGETITLE kwenye {{SITENAME}}',
-'enotif_lastvisited' => 'Tazama mabadiliko yote tangu ziara yako iliyopita kwenye ukurasa wa $1.',
+'enotif_lastvisited' => 'Tazama $1 kwa mabadiliko yote tangu ziara yako ya mwisho.',
 'enotif_lastdiff' => 'Tazama badiliko hili hapo $1.',
 'enotif_anon_editor' => 'mtumiaji bila jina $1',
 'enotif_body' => 'Mpendwa $WATCHINGUSERNAME,
@@ -2021,7 +2062,7 @@ Kutoa maoni yako au kupata msaada mwingine:
 'deletepage' => 'Futa ukurasa',
 'confirm' => 'Yakinisha',
 'excontent' => "iliyokuwemo: '$1'",
-'excontentauthor' => 'yaliyomo yalikuwa: "$1" (yaliyechangiwa na mchangiaji mmoja tu anayeitwa "[[Special:Contributions/$2|$2]]")',
+'excontentauthor' => 'Yaliyomo yalikuwa: "$1" (na mchangiaji mmoja tu anayeitwa "[[Special:Contributions/$2|$2]]")',
 'exbeforeblank' => 'maandishi kabla hayajafutwa yote yalikuwa: "$1"',
 'exblank' => 'ukurasa ulikuwa tupu',
 'delete-confirm' => 'Futa "$1"',
@@ -2033,7 +2074,7 @@ Tafadhali hakikisha kwamba unalenga kufanya hivyo, na kwamba unaelewa matokeo ya
 'actionfailed' => 'Tendo halikufaulu',
 'deletedtext' => '"$1" imefutwa. Ona $2 kwa historia ya kurasa zilizofutwa hivi karibuni.',
 'dellogpage' => 'Kumbukumbu ya ufutaji',
-'dellogpagetext' => 'Kurasa na mafaili zilizofutwa hivi karibuni zinaorodheshwa chini.',
+'dellogpagetext' => 'Hapa chini ni orodha ya mafaili yaliyofutwa hivi karibuni.',
 'deletionlog' => 'kumbukumbu za kufuta',
 'reverted' => 'Ilirejeshwa hadi pitio la zamani',
 'deletecomment' => 'Sababu:',
@@ -2063,16 +2104,16 @@ ukurasa huu una mhariri mmoja tu.',
 'rollback-success' => 'Masahihisho aliyeyafanya $1 yalirejeshwa hadi kufika sahihisho la mwisho aliyefanya $2.',
 
 # Edit tokens
-'sessionfailure-title' => 'Kushindikana cha kipindi',
+'sessionfailure-title' => 'Kushindikana kwa kipindi',
 
 # Protect
 'protectlogpage' => 'Kumbukumbu ya ulindaji',
 'protectedarticle' => 'aliulinda "[[$1]]"',
 'modifiedarticleprotection' => 'alibadilisha kiwango cha ulindaji kwa ajili ya "[[$1]]"',
-'unprotectedarticle' => 'alitoa ulindaji wa "[[$1]]"',
-'movedarticleprotection' => 'alihamisha ulindaji wa "[[$2]]" hadi "[[$1]]"',
+'unprotectedarticle' => 'ulindaji ulitolewa kutoka kwa "[[$1]]"',
+'movedarticleprotection' => 'mpangilio wa ulindaji wa kuhamishwa kutoka "[[$2]]" hadi "[[$1]]"',
 'protect-title' => 'Kubadilisha kiwango cha ulindaji wa "$1"',
-'protect-title-notallowed' => 'Tazama kiwango cha ulindaji wa "$1"',
+'protect-title-notallowed' => 'Tazama kiwango cha ulindaji cha "$1"',
 'prot_1movedto2' => 'alihamisha [[$1]] hadi [[$2]]',
 'protect-badnamespace-title' => 'Eneo la wiki lisiloweza kulindwa',
 'protect-badnamespace-text' => 'Kurasa zilizopo katika eneo hili la wiki haziwezi kulindwa',
@@ -2081,7 +2122,7 @@ ukurasa huu una mhariri mmoja tu.',
 'protectexpiry' => 'Itakwisha:',
 'protect_expiry_invalid' => 'Muda wa kwisha ni batilifu.',
 'protect_expiry_old' => 'Muda wa kuishi umepita tayari.',
-'protect-unchain-permissions' => 'Fungua chaguzi zingine za ulinzi',
+'protect-unchain-permissions' => 'Fungua chaguzi zingine za ulindaji',
 'protect-text' => "Unaweza kutazama na kubadilisha kiwango cha ulindaji hapa kwa ukurasa '''$1'''.",
 'protect-locked-dblock' => "Viwango vya ulindaji haviwezi kubadilishwa kwa sababu hifadhidata imefungwa.
 Hapo panaandikwa viwango vya ulindaji wa ukurasa '''$1''':",
@@ -2246,9 +2287,12 @@ Andika sababu ya kuzuia chini (kwa mfano, kwa kutaja mifano ya kurasa zilizohari
 'ipb-unblock-addr' => 'Acha kumzuia $1',
 'ipb-unblock' => 'Acha kumzuia mtumiaji au anwani wa IP',
 'ipb-blocklist-contribs' => 'Michango ya $1',
+'unblockip' => 'Acha kuzuia mtumiaji',
 'blocklist' => 'Watumiaji waliozuiliwa',
 'ipblocklist' => 'Watumiaji waliozuiliwa',
+'ipblocklist-legend' => 'Tafuta mtumiaji aliyezuiwa',
 'blocklist-timestamp' => 'Tarehe na saa',
+'blocklist-target' => 'Lengo',
 'blocklist-expiry' => 'Itakwisha',
 'blocklist-reason' => 'Sababu',
 'ipblocklist-submit' => 'Tafuta',
@@ -2256,10 +2300,12 @@ Andika sababu ya kuzuia chini (kwa mfano, kwa kutaja mifano ya kurasa zilizohari
 'infiniteblock' => 'milele',
 'expiringblock' => 'inakwisha tarehe $1 saa $2',
 'emailblock' => 'barua pepe imezuiliwa',
+'ipblocklist-empty' => 'Orodha ya kuzuiwa ni tupu.',
 'blocklink' => 'zuia',
 'unblocklink' => 'acha kuzuia',
 'change-blocklink' => 'badilisha zuia',
 'contribslink' => 'michango',
+'emaillink' => 'tuma barua pepe',
 'blocklogpage' => 'Kumbukumbu ya uzuio',
 'blocklogentry' => 'amemzuia [[$1]] mpaka $2 $3',
 'unblocklogentry' => 'aliachisha kuzuia $1',
@@ -2271,10 +2317,10 @@ Andika sababu ya kuzuia chini (kwa mfano, kwa kutaja mifano ya kurasa zilizohari
 'proxyblocksuccess' => 'Tayari.',
 
 # Developer tools
-'lockdb' => 'Kufunga hifadhidata',
-'unlockdb' => 'Kufungua hifadhidata',
-'lockconfirm' => 'Ndiyo, kwa kweli nataka kufunga hifadhidata.',
-'unlockconfirm' => 'Ndiyo, kwa kweli nataka kufungua hifadhidata.',
+'lockdb' => 'Funga hifadhidata',
+'unlockdb' => 'Fungua hifadhidata',
+'lockconfirm' => 'Ndiyo, ni kweli nataka kufunga hifadhidata.',
+'unlockconfirm' => 'Ndiyo,nataka kufungua hifadhidata.',
 'lockbtn' => 'Funga hifadhidata',
 'unlockbtn' => 'Fungua hifadhidata',
 'locknoconfirm' => 'Hujaweka alama katika sanduku la kuitika kitendo.',
@@ -2629,14 +2675,18 @@ likifupishwa. Nyuga zingine zitafichwa kama chaguo-msingi.
 'exif-lightsource' => 'Mwanga',
 'exif-flash' => 'Taa ya picha',
 'exif-flashenergy' => 'Nguvu ya taa ya picha',
+'exif-filesource' => 'Chanzo cha faili',
 'exif-gpslatituderef' => 'Latitudo kwenda kaskazini au kusini',
 'exif-gpslatitude' => 'Latitudo',
 'exif-gpslongituderef' => 'Longitudo kwenda mashariki au magharibi',
 'exif-gpslongitude' => 'Longitudo',
 'exif-gpsaltituderef' => 'Rejeo ya mwinuko',
 'exif-gpsaltitude' => 'Mwinuko',
+'exif-gpsmeasuremode' => 'Jinsi ya kupima',
 'exif-gpsspeedref' => 'Kizio cha kupima mwendo',
 'exif-gpsspeed' => 'Mwendo wa sanduku la GPS',
+'exif-gpstrack' => 'Mwelekeo wa harakati',
+'exif-gpsimgdirection' => 'Mwelekeo wa picha',
 'exif-gpsdestlatitude' => 'Latitudo ya kikomo',
 'exif-gpsdestlongitude' => 'Longitudo ya kikomo',
 'exif-gpsdestdistance' => 'Mbali wa kikomo',
@@ -2648,6 +2698,7 @@ likifupishwa. Nyuga zingine zitafichwa kama chaguo-msingi.
 'exif-countrycreated' => 'Nchi palipopigwa picha',
 'exif-countrycodecreated' => 'Kodi ya nchi picha palipopigwa',
 'exif-countrydest' => 'Nchi inayoonyeshwa',
+'exif-citydest' => 'Mji umeonyeshwa',
 'exif-objectname' => 'Jina fupi',
 'exif-specialinstructions' => 'Maelekezo maalum',
 'exif-headline' => 'Kichwa cha habari',
@@ -2657,19 +2708,27 @@ likifupishwa. Nyuga zingine zitafichwa kama chaguo-msingi.
 'exif-languagecode' => 'Lugha',
 'exif-iimcategory' => 'Jamii',
 'exif-datetimeexpires' => 'Usitumie baada ya',
+'exif-datetimereleased' => 'Ilitolewa mnamo',
 'exif-cameraownername' => 'Mwenye kamera',
+'exif-label' => 'Lebo',
 'exif-copyrighted' => 'Hali ya hakimiliki',
 'exif-copyrightowner' => 'Mwenye hatimiliki',
 'exif-pngfilecomment' => 'Maoni juu ya faili la PNG',
+'exif-disclaimer' => 'Kanusho',
+'exif-contentwarning' => 'Ilani ya maduhui',
 'exif-giffilecomment' => 'Maoni juu ya faili la GIF',
+'exif-intellectualgenre' => 'Aina ya kifaa',
 'exif-personinimage' => 'Mtu aliyepigwa picha',
 
+'exif-copyrighted-true' => 'Yenye hatimiliki',
+
 'exif-unknowndate' => 'Tarehe haijulikani',
 
 'exif-orientation-1' => 'Kawaida',
 
 'exif-componentsconfiguration-0' => 'haipo',
 
+'exif-exposureprogram-0' => 'Haijafafanuliwa',
 'exif-exposureprogram-1' => 'Kwa mikono',
 'exif-exposureprogram-2' => 'Programu ya kawaida',
 
@@ -2694,6 +2753,8 @@ likifupishwa. Nyuga zingine zitafichwa kama chaguo-msingi.
 
 'exif-focalplaneresolutionunit-2' => 'inchi',
 
+'exif-customrendered-0' => 'Mchakato wa kawaida',
+
 'exif-scenecapturetype-0' => 'Kawaida',
 'exif-scenecapturetype-1' => 'Mandhari',
 'exif-scenecapturetype-2' => 'Watu',
@@ -2734,7 +2795,7 @@ likifupishwa. Nyuga zingine zitafichwa kama chaguo-msingi.
 # Pseudotags used for GPSDestDistanceRef
 'exif-gpsdestdistance-k' => 'Kilomita',
 'exif-gpsdestdistance-m' => 'Maili',
-'exif-gpsdestdistance-n' => 'Maili ya baharia',
+'exif-gpsdestdistance-n' => 'Maili ya bahari',
 
 'exif-gpsdop-excellent' => 'Nzuri sana ($1)',
 'exif-gpsdop-good' => 'Nzuri ($1)',
@@ -2756,10 +2817,12 @@ likifupishwa. Nyuga zingine zitafichwa kama chaguo-msingi.
 'exif-dc-date' => 'Tarehe',
 'exif-dc-publisher' => 'Mchapishaji',
 'exif-dc-rights' => 'Haki',
+'exif-dc-source' => 'Chanzo cha media',
+'exif-dc-type' => 'Aina ya media',
 
-'exif-rating-rejected' => 'Lilikataliwa',
+'exif-rating-rejected' => 'Ilikataliwa',
 
-'exif-isospeedratings-overflow' => 'Zaidi na 65535',
+'exif-isospeedratings-overflow' => 'Zaidi ya 65535',
 
 'exif-iimcategory-ace' => 'Sanaa, utamaduni na burudani',
 'exif-iimcategory-clj' => 'Uhalifu na sheria',
@@ -2774,6 +2837,7 @@ likifupishwa. Nyuga zingine zitafichwa kama chaguo-msingi.
 'exif-iimcategory-pol' => 'Siasa',
 'exif-iimcategory-rel' => 'Dini na imani',
 'exif-iimcategory-sci' => 'Sayansi na teknolojia',
+'exif-iimcategory-soi' => 'Masuala ya kijamii',
 'exif-iimcategory-spo' => 'Michezo',
 'exif-iimcategory-war' => 'Vita, migogoro na vurugu',
 'exif-iimcategory-wea' => 'Hali ya hewa',
@@ -2948,6 +3012,7 @@ Pia unaweza [[Special:EditWatchlist|kutumia kihariri cha kawaida]].',
 # Special:Version
 'version' => 'Toleo',
 'version-specialpages' => 'Kurasa maalum',
+'version-variables' => 'Vibadili',
 'version-skins' => 'Maumbo',
 'version-other' => 'Zingine',
 'version-version' => '(Toleo $1)',
@@ -2962,6 +3027,7 @@ Huwa unapokea [{{SERVER}}{{SCRIPTPATH}}/COPYING nakala ya GNU General Public Lic
 'version-software' => 'Bidhaa pepe iliyosakinishwa',
 'version-software-product' => 'Bidhaa',
 'version-software-version' => 'Toleo',
+'version-entrypoints-header-url' => 'KISARA Kioneshi Sanifu Raslimali',
 
 # Special:FilePath
 'filepath' => 'Njia ya faili',
@@ -3066,6 +3132,12 @@ Tovuti hii inapata matatatizo wakati huu.',
 'logentry-suppress-revision' => '$1 alibadilisha kwa siri hali ya kuonekana wazi {{PLURAL:$5|kumbukumbu $5 ya|kumbukumbu $5 za}} ukurasa wa $3: $4',
 'logentry-suppress-event-legacy' => '$1 alibadilisha kwa siri hali ya kuonekana wazi ya kumbukumbu za $3',
 'logentry-suppress-revision-legacy' => '$1 alibadilisha kwa siri hali ya kuonekana wazi mapitio ya ukurasa $3',
+'revdelete-content-hid' => 'maudhui yamefichwa',
+'revdelete-summary-hid' => 'historia ya kuhariri imefichwa',
+'revdelete-uname-hid' => 'jina la mtumiaji limefichwa',
+'revdelete-content-unhid' => 'maudhui hayajafichwa',
+'revdelete-summary-unhid' => 'muhtasari wa kuhariri haujafichwa',
+'revdelete-uname-unhid' => 'jina la mtumiaji halijafichwa',
 'revdelete-restricted' => 'aliwazuia pia wakabidhi wasiyaone maelezo',
 'revdelete-unrestricted' => 'aliwarudishia wakabidhi uwezo wa kuona maelezo',
 'logentry-move-move' => '$1 alihamisha ukurasa wa $3 hadi $4',
@@ -3088,9 +3160,23 @@ Tovuti hii inapata matatatizo wakati huu.',
 'feedback-close' => 'Tayari',
 
 # API errors
+'api-error-empty-file' => 'Faili ulilowasilisha ni tupu.',
+'api-error-emptypage' => 'Kutengeneza mpya, kurasa tupu hazikubaliwi',
+'api-error-filename-tooshort' => 'Jina la faili ni fupi mno.',
+'api-error-filetype-banned' => 'Aina hili la faili hairuhusiwi.',
+'api-error-illegal-filename' => 'Jina hilo la faili haliruhusiwi.',
 'api-error-unclassified' => 'Ilitokea hitilafu isiyojulikana.',
 'api-error-unknown-code' => 'Hitilafu isiyojulikana: "$1".',
 'api-error-unknown-warning' => 'Ilani isiyojulikana: "$1".',
 'api-error-unknownerror' => 'Hitilafu isiyojulikana: "$1".',
 
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|second|sekunde}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minute|dakikas}}',
+'duration-hours' => '$1 {{PLURAL:$1|hour|masaa}}',
+'duration-days' => '$1 {{PLURAL:$1|day|masiku}}',
+'duration-weeks' => '$1 {{PLURAL:$1|week|wiki}}',
+'duration-years' => '$1 {{PLURAL:$1|year|miaka}}',
+'duration-centuries' => '$1 {{PLURAL:$1|century|karne}}',
+
 );
index ff30abf..4dc180b 100644 (file)
@@ -8,6 +8,8 @@
  * @file
  *
  * @author Aswn
+ * @author Balajijagadesh
+ * @author Caliberoviv
  * @author Kaganer
  * @author Kanags
  * @author Karthi.dr
@@ -344,6 +346,8 @@ $1',
 'youhavenewmessages' => 'உங்களுக்குப் $1 உள்ளன ($2).',
 'newmessageslink' => 'புதிய செய்திகள்',
 'newmessagesdifflink' => 'கடைசி மாற்றம்',
+'newmessageslinkplural' => '{{PLURAL:$1|ஒரு புதிய செய்தி|புதிய செய்திகள்}}',
+'newmessagesdifflinkplural' => 'கடைசி {{PLURAL:$1|மாற்றம்|மாற்றங்கள்}}',
 'youhavenewmessagesmulti' => '$1 இல் உங்களுக்கு புதிய செய்திகள் காத்திருக்கின்றன',
 'editsection' => 'தொகு',
 'editold' => 'தொகு',
@@ -434,6 +438,8 @@ MySQL returned error "$3: $4".',
 'badarticleerror' => 'இச்செயற்பாட்டை இப்பக்கத்தில் செயற்படுத்த முடியாது.',
 'cannotdelete' => '"$1" பக்கத்தையோ கோப்பையோ நீக்க முடியாது. (வேறு யாராலோ ஏற்கெனவே நீக்கப்பட்டிருக்கலாம்.)',
 'cannotdelete-title' => '"$1" பக்கத்தை நீக்க முடியாது',
+'delete-hook-aborted' => 'நீக்கல்  hook ஆல் தடைசெய்யப்பட்டது.
+அது எந்த விளக்கமும் அளிக்கவில்லை.',
 'badtitle' => 'பழுதுள்ள தலைப்பு',
 'badtitletext' => 'கோரப்பட்ட பக்கத்தின் தலைப்பு செல்லாது, வெறுமை, அல்லது பிழையாக இணைக்கப்பட்ட மொழிகளிடை அல்லது விக்கியிடைத் தலைப்பாகும்.',
 'perfcached' => 'பின்வரும் தரவுகள் இடைக்கிடங்கில் உள்ளன,  தரவுகள் புதுப்பித்தநிலையில் இல்லாமல் இருக்கலாம். அதிக அளவாக  {{PLURAL:$1|ஒரு முடிவு|$1 முடிவுகள்}} இடைக்கிடங்கில் இருக்கலாம்.',
@@ -459,6 +465,11 @@ MySQL returned error "$3: $4".',
 'ns-specialprotected' => 'சிறப்புப் பக்கங்களைத் தொகுக்க முடியாது.',
 'titleprotected' => "பயனர் [[User:$1|$1]] இத்தலைப்பு உருவாக்கப்படுவதை தவிர்க்கும் வகையில் தடுத்துள்ளார்.
 கொடுக்கப்பட்டக் காரணம் ''$2''.",
+'filereadonlyerror' => '"$1" கோப்பைத் திருத்த முடியவில்லை ஏனெனில் கோப்புப் பெட்டகம் "$2" படிக்க-மட்டும் வகையில் உள்ளது. அதனை பூட்டிய நிர்வாகி பின்வரும் விளக்கத்தை அளித்துள்ளார்: "$3"',
+'invalidtitle-knownnamespace' => "பெயரிடைவெளி ' $2 '' மற்றும் உரை '' $3 '' கொன்ட தலைப்பு செல்லாது",
+'invalidtitle-unknownnamespace' => 'அறியப்படாத பெயரிடைவெளி $1 மற்றும் உரை $2 கொண்ட தலைப்பு செல்லாது',
+'exception-nologin' => 'புகுபதிகை செய்யப்படவில்லை.',
+'exception-nologin-text' => 'இந்த பக்கம் / செய்கை இந்த விக்கியில் உங்களது புகுபதிகையை எதிர்பார்க்கிறது.',
 
 # Virus scanner
 'virus-badscanner' => "சரியற்ற உள்ளமைவு: அறியப்படாத வைரஸ் வருடி: '' $1 ''",
@@ -477,6 +488,7 @@ MySQL returned error "$3: $4".',
 'remembermypassword' => 'எனது கடவுச்சொல்லை (கூடியது $1 {{PLURAL:$1|நாள்|நாட்கள்}}) அமர்வுகளிடையே நினைவில் வைத்திருக்கவும்.',
 'securelogin-stick-https' => 'புகுபதிகைக்குப் பிறகும் HTTPS-இலேயே இருக்கவும்',
 'yourdomainname' => 'உங்கள் உரிமைப்பரப்பு:',
+'password-change-forbidden' => 'நீங்கள் விக்கிகளில் கடவுச் சொற்களை மாற்ற முடியாது',
 'externaldberror' => 'வெளி உறுதிப்படுத்தலில் ஏற்பட்ட தவறு காரணமாக உங்கள் வெளி கணக்கை இற்றைப்படுத்த முடியாது.',
 'login' => 'புகுபதிகை',
 'nav-login-createaccount' => 'புகுபதிகை/பயனர் கணக்கு தொடக்கம்',
@@ -542,6 +554,7 @@ MySQL returned error "$3: $4".',
 'emailconfirmlink' => 'உங்கள் மின்னஞ்சல் முகவரியை உறுதிப்படுத்தவும்',
 'invalidemailaddress' => 'மின்னஞ்சல் முகவரி பிழையான கட்டமைப்பைக் கொண்டுள்ளப்படியால் மின்னஞ்சல் முகவரியை ஏற்றுக்கொள்ள முடியாது. தயவுசெய்து  சரியான கட்டமைப்புடன் கூடிய மின்னஞ்சல் முகவரியை இடவும் அல்லது அவ்விடத்தை வெற்றாக விடவும்.',
 'cannotchangeemail' => 'கணக்கின் மின்னஞ்சல் முகவரிகளை இந்த விக்கிஇல் மாற்ற முடியாது.',
+'emaildisabled' => 'இந்த வலைதளம் மின்அஞ்சல்களை அனுப்ப முடியாது',
 'accountcreated' => 'கணக்கு உருவாக்கப்பட்டது.',
 'accountcreatedtext' => '$1 என்ற பெயரில் பயனர் கணக்கு உருவாக்கப்பட்டது.',
 'createaccount-title' => '{{SITENAME}} தளத்துக்கான கணக்கு தொடக்கம்',
@@ -704,6 +717,8 @@ $1 பயனரையோ அல்லது வேறு [[{{MediaWiki:Grouppage
 'noarticletext-nopermission' => 'தற்பொழுது இப்பக்கத்தில் உரை எதுவும் இல்லை.
 நீங்கள் [[Special:Search/{{PAGENAME}}|பக்கத் தலைப்பை வைத்து]] அல்லது மற்ற பக்கங்களில்,
 or <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} அல்லது தேடுதல் தொடர்பான பதிவுகளில் தேடவும்.]</span>.',
+'missing-revision' => "இந்த பரிசீலனை # $1  '' {{PAGENAME}}' பெயருள்ள பக்கத்தின் இல்லை.!N வேடிக்கையானN!இது தான் வழக்கமாக ஏற்பட்டிருக்கலாம் நீக்கப்பட்டுள்ளது பக்கத்திற்கு outdated வரலாறு இணைப்பை தொடர்ந்து.
+விவரங்கள் முடியும் கண்டறிய, [{{fullurl: {{# சிறப்பு: குறிப்பேடு}} / delete|page = {{FULLPAGENAMEE}}}} நீக்குதல் குறிப்பேடு].",
 'userpage-userdoesnotexist' => '"<nowiki>$1</nowiki>" என்றக் கணக்கு இன்னமும் பதிவுச் செய்யப்படவில்லை. இதை உருவாக்க/தொகுக்க வேண்டுமா என்பதை உறுதிப்படுத்தவும்.',
 'userpage-userdoesnotexist-view' => 'பயனர் கணக்கு "$1" பதியப்படவில்லை',
 'blocked-notice-logextract' => 'இந்தப் பயனர் தற்சமயம் தடை செய்யப்பட்டுள்ளார். இவரது தடை பதிகையின் அண்மைய மாற்றம் கீழே தரப்பட்டுள்ளது:',
@@ -818,6 +833,11 @@ or <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}
 'parser-template-loop-warning' => 'வார்ப்புருவின் முழுச்சுற்று உணரப்பட்டுள்ளது: [[$1]]',
 'parser-template-recursion-depth-warning' => 'வார்ப்புரு மறுநிகழ்வு (recursion) ஆழம் வரம்பை மீறிவிட்டது ( $1 )',
 'language-converter-depth-warning' => 'மொழி மாற்றியின் ஆழம் வரம்பை மீறிவிட்டது ( $1 )',
+'node-count-exceeded-category' => 'பக்கங்கள் எங்கு முடிச்சு எண்ணிக்கை மிகவும் அதிகரித்துள்ளது',
+'node-count-exceeded-warning' => 'பக்க வரம்பை மீறிவிட்டது முடிச்சு எண்ணிக்கை',
+'expansion-depth-exceeded-category' => 'பக்கங்கள் எங்கு விரிவு ஆழம் மீறிவிட்டது',
+'expansion-depth-exceeded-warning' => 'விரிவு ஆழம் பக்க வரம்பை மீறிவிட்டது',
+'converter-manual-rule-error' => 'கைமுறை மொழி மாற்றம் விதியில் பிழை கண்டுபிடிக்கப்பட்டது',
 
 # "Undo" feature
 'undo-success' => "இத்தொகுப்பை மீளமைக்க முடியும். தயவு செய்து, கீழே காட்டப்பட்டுள்ள ஒப்பீட்டைப் பார்த்து, நீங்கள் செய்ய முயற்சிப்பது இதுதானா? என்பதை உறுதிப்படுத்திக் கொண்டு '''பக்கத்தைச் சேமிக்கவும்''' என்பதன் மேல் சொடுக்கவும்.",
@@ -1275,6 +1295,7 @@ $1",
 'right-writeapi' => 'எழுது API பயன்படுத்தவும்',
 'right-delete' => 'பக்கங்களை நீக்குக',
 'right-bigdelete' => 'பெரிய வரலாற்றைக் கொண்ட பக்கங்களை நீக்கல்',
+'right-deletelogentry' => 'குறிப்பிட்ட குறிப்பேடு உள்ளீடுகள் நீக்கியதை மீட்டல் மற்றும் நீக்குதல்',
 'right-deleterevision' => 'பக்கமொன்றின் குறித்த திருத்தங்களை நிக்கல் மீட்டல்',
 'right-deletedhistory' => 'நீக்கிய வரலாறு உள்ளீடுகளை காண்,அதனுடன் தொடர்புடைய உரை  இல்லாமல்',
 'right-deletedtext' => ' நீக்கப்பட்ட உரை மற்றும் நீக்கப்பட்ட பரிசீலனைகளுக்கு இடையேயான மாற்றங்களை காண்.',
@@ -1739,6 +1760,7 @@ $1',
 'statistics-edits' => '{{SITENAME}} அமைக்கப்பட்டதிலிருந்து பக்க திருத்தங்கள்',
 'statistics-edits-average' => 'ஒரு பக்கத்திற்கான சராசரி தொகுப்புக்கள்',
 'statistics-views-total' => 'பார்வையிடப்பட்டதின் மொத்த எண்ணிக்கை',
+'statistics-views-total-desc' => 'தற்பொழுது இல்லாத மற்றும் சிறப்பு பக்கங்களின் காட்சிகள் இதில் சேர்க்கபடவில்லை',
 'statistics-views-peredit' => 'ஒரு தொகுத்தலுக்காக பார்க்கப்பட்ட எண்ணிக்கை',
 'statistics-users' => 'பதிவு செய்யப்பட்ட [[Special:ListUsers|பயனர்கள்]]',
 'statistics-users-active' => 'தொடர் பங்களிப்பாளர்கள் (பயனர்கள்)',
@@ -1773,6 +1795,8 @@ $1',
 'nmembers' => '$1 {{PLURAL:$1|உறுப்பினர்|உறுப்பினர்கள்}}',
 'nrevisions' => '{{PLURAL:$1|ஒரு திருத்தம்|$1 திருத்தங்கள்}}',
 'nviews' => '{{PLURAL:$1|ஒரு பார்வை|$1 பார்வைகள்}}',
+'nimagelinks' => '$1 {{PLURAL:$!|பக்கத்தில்|பக்கங்களில்}} பயன்படுத்தப்பட்டது',
+'ntransclusions' => '$1 {{PLURAL:$1|பக்கத்தில்|பக்கங்களில்}} பயன்படுத்தப்பட்டது',
 'specialpage-empty' => 'இந்தப் புகாருக்குகந்த முடிவுகள் எதுவுமில்லை.',
 'lonelypages' => 'உறவிலிப் பக்கங்கள்',
 'lonelypagestext' => 'கீழ்கண்ட பக்கங்கள், {{SITENAME}}-இத்தளத்தின் மற்ற பக்கங்களில் எந்தவிதத்திலும் இணைக்கப்படவில்லை.',
@@ -1845,6 +1869,7 @@ $1',
 'alllogstext' => '{{SITENAME}} தளத்தின் பதிவுகள் அனைத்திற்குமான ஒருங்கிணைந்த காட்சி. பதிவு வகை, பயனர் பெயர், அல்லது தொடர்புடைய பக்கத்தைத் தெரிவு செய்வதன்மூலம் காட்சி நோக்கை சுருக்கிக் கொள்ள முடியும்.',
 'logempty' => 'பொருத்தமான பதிவுகள் யாதுமில்லை.',
 'log-title-wildcard' => 'இவ்வுரையுடன் தொடங்கும் தலைப்புகளைத் தேடு',
+'showhideselectedlogentries' => 'தேர்ந்தெடுத்த குறிப்பேடு உள்ளீடுகள் காண்பி/மறை',
 
 # Special:AllPages
 'allpages' => 'அனைத்துப் பக்கங்கள்',
@@ -1862,6 +1887,7 @@ $1',
 'allpagesprefix' => 'பின்வரும் முன்னொட்டு உடைய பக்கங்களை காட்டு:',
 'allpagesbadtitle' => 'கொடுக்கப்பட்ட தலைப்பு செல்லுபடியற்றது அல்லது பிழையான விக்கியிடை அல்லது மொழி முன்னொட்டைக் கொண்டுள்ளது. இது தலைப்புக்களில் பயன்படுத்த முடியாத எழுத்துக்களையும் கொண்டிருக்கலாம்.',
 'allpages-bad-ns' => '{{SITENAME}} தளத்தில் "$1" பெயர்வெளி கிடையாது.',
+'allpages-hide-redirects' => 'வழிமாற்றுகளைப் மறை',
 
 # SpecialCachedPage
 'cachedspecial-refresh-now' => 'அண்மையான பதிப்பை காண்க',
@@ -1909,6 +1935,8 @@ $1',
 
 # Special:ListGroupRights
 'listgrouprights' => 'பயனர் குழு உரிமைகள்',
+'listgrouprights-key' => '<span class="listgrouprights-granted">உரிமை வழங்கப்பட்டது</span>
+ * <span class="listgrouprights-revoked">உரிமை பறிக்கபட்டது</span>',
 'listgrouprights-group' => 'குழு',
 'listgrouprights-rights' => 'உரிமைகள்',
 'listgrouprights-helppage' => 'Help:குழு உரிமைகள்',
@@ -2066,6 +2094,7 @@ $NEWPAGE
 'rollback' => 'முன்நிலையாக்கத் தொகுப்புகள்',
 'rollback_short' => 'முன்நிலையாக்கு',
 'rollbacklink' => 'முன்நிலையாக்கு',
+'rollbacklinkcount' => '$1 {{PLURAL:$1|தொகுப்பை|தொகுப்புகளை}} முன்நிலையாக்குக',
 'rollbackfailed' => 'முன்நிலையாக்கம் தோல்வி',
 'cantrollback' => 'தொகுப்பை முன்நிலையாக்க முடியாது; கடைசிப் பங்களிப்பாளரே இக்கட்டுரையின் ஒரே ஆசிரியராகும்.',
 'alreadyrolled' => '[[User:$2|$2]] ([[User talk:$2|Talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) பயனரால் செய்யப்பட்ட [[:$1]] இன் கடைசித் தொகுப்பை முன்நிலையாக்க முடியாது; வேறு யாரோ இப்பக்கத்தை ஏற்கெனவே தொகுத்தோ அல்லது முன்நிலையாக்கியோ உள்ளார்.
@@ -2585,6 +2614,7 @@ $1',
 'javascripttest' => 'சாவாநிரல் சோதனை நடக்கின்றது',
 'javascripttest-disabled' => 'இந்தச் செயல்பாடு முடக்கப்பட்டுள்ளது.',
 'javascripttest-title' => '$1 சோதனைகள் நடக்கின்றன',
+'javascripttest-pagetext-noframework' => 'இந்த பக்கம் JavaScript பரிசோதனை ஓட்டத்திற்காக ஒதுக்கப்பட்டுள்ளது',
 'javascripttest-pagetext-skins' => 'சோதனைகளை நடத்த முகப்புறை ஒன்றைத் தேர்வுசெய்:',
 
 # Tooltip help for the actions
@@ -3363,6 +3393,8 @@ $5
 'version-software' => 'நிறுவப்பட்ட மென்பொருள்',
 'version-software-product' => 'உற்பத்திப்பொருள்',
 'version-software-version' => 'பதிப்பு',
+'version-entrypoints' => 'நுழைவு புள்ளி உரலிகள்',
+'version-entrypoints-header-entrypoint' => 'நுழைவு புள்ளி',
 'version-entrypoints-header-url' => 'உரலி (URL)',
 
 # Special:FilePath
@@ -3525,6 +3557,8 @@ $5
 'api-error-empty-file' => 'நீங்கள் அளித்த கோப்பு காலியாக உள்ளது.',
 'api-error-emptypage' => 'புதிய, காலி பக்கங்கள் உருவாக்கல் அனுமதிக்கப்படவில்லை.',
 'api-error-fetchfileerror' => 'உள்ளகப் பிழை: கோப்பைப் பெறுகையில் ஏதோ தவறு நேர்ந்துவிட்டது.',
+'api-error-fileexists-forbidden' => '"$1" என்ற பெயருள்ள கோப்பு ஏற்கனவே உள்ளது.  மேலெழுத முடியாது.',
+'api-error-fileexists-shared-forbidden' => '"$1" என்ற பெயருள்ள கோப்பு ஏற்கனவே கோப்பு பகிர்மானப் பெட்டகத்தில்  உள்ளது. மேலெழுத முடியாது.',
 'api-error-file-too-large' => 'நீங்கள் அளித்த கோப்பு மிகவும் பெரியதாக உள்ளது.',
 'api-error-filename-tooshort' => 'கோப்புப் பெயர் மிகவும் சிறியதாக உள்ளது.',
 'api-error-filetype-banned' => 'இக்கோப்பு வகை தடைசெய்யப்பட்டுள்ளது.',
@@ -3552,6 +3586,17 @@ $5
 'api-error-uploaddisabled' => 'இந்த விக்கியில் பதிவேற்றல் செயலிழக்கச் செய்யப்பட்டுள்ளது.',
 'api-error-verification-error' => 'இக்கோப்பு பிழையுடனோ தவறான விரிவுடனோ இருக்கலாம்.',
 
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|நொடி|நொடிகள்}}',
+'duration-minutes' => '{{PLURAL: $1|நிமிடம்|நிமிடங்கள்}}',
+'duration-hours' => '$1 {{PLURAL:$1|மணி|மணிகள்}} முன்பு',
+'duration-days' => '$1 {{PLURAL:$1|நாள்|நாட்கள்}}',
+'duration-weeks' => '{{PLURAL: $1|வாரம்|வாரங்கள்}}',
+'duration-years' => '{{PLURAL: $1|வருடம்|வருடங்கள்}}',
+'duration-decades' => '$1 {{PLURAL:$1|பத்தாண்டு|பத்தாண்டுகள்}}',
+'duration-centuries' => '$1 {{PLURAL:$1|நூற்றாண்டு|நூற்றாண்டுகள்}}',
+'duration-millennia' => '$1 {{PLURAL:$1|ஆயிரம் ஆண்டு|ஆயிரம் ஆண்டுகள்}}',
+
 # Unknown messages
 'api-error-filetype-banned-type' => '$1  {{PLURAL:$4|அனுமதிக்கப்படாத கோப்பு வகையாகும் | அனுமதிக்கப்படாத கோப்பு வகைகளாகும்}}.. அனுமதிக்கப்பட்ட {{PLURAL:$3|கோப்புவகை|கோப்புவகைகள்}} $2 என்பது(வை) ஆகும்.',
 );
index 5f3e231..c0553c4 100644 (file)
@@ -149,7 +149,7 @@ $messages = array(
 'tog-hidepatrolled' => 'ఇటీవలి మార్పులలో నిఘా ఉన్న మార్పులను దాచిపెట్టు',
 'tog-newpageshidepatrolled' => 'కొత్త పేజీల జాబితా నుంచి నిఘా ఉన్న పేజీలను దాచిపెట్టు',
 'tog-extendwatchlist' => 'కేవలం ఇటీవలి మార్పులే కాక, మార్పులన్నీ చూపించటానికి నా వీక్షణా జాబితాను పెద్దది చేయి',
-'tog-usenewrc' => 'à°®à±\86à°°à±\81à°\97à±\88à°¨ à°\87à°\9fà±\80వలి à°®à°¾à°°à±\8dà°ªà±\81à°² à°ªà±\87à°\9cà±\80 (జావాస్క్రిప్టు అవసరం)',
+'tog-usenewrc' => 'à°\87à°\9fà±\80వలి à°®à°¾à°°à±\8dà°ªà±\81à°²à±\81 à°®à°°à°¿à°¯à±\81 à°µà°¿à°\95à±\8dà°·à°£ à°\9cాబితాలలà±\8b à°®à°¾à°°à±\8dà°ªà±\81లనà±\81 à°ªà±\87à°\9cà±\80 à°µà°¾à°°à°¿à°\97à°¾ à°\9aà±\82పిà°\82à°\9aà±\81 (జావాస్క్రిప్టు అవసరం)',
 'tog-numberheadings' => 'శీర్షికలకు ఆటోమాటిక్‌గా వరుస సంఖ్యలు పెట్టు',
 'tog-showtoolbar' => 'దిద్దుబాట్లు చేసేటప్పుడు, అందుకు సహాయపడే పరికరాలపెట్టెను చూపించు (జావాస్క్రిప్టు)',
 'tog-editondblclick' => 'డబుల్‌ క్లిక్కు చేసినప్పుడు పేజీని మార్చు (జావాస్క్రిప్టు)',
@@ -157,8 +157,8 @@ $messages = array(
 'tog-editsectiononrightclick' => 'విభాగం పేరు మీద కుడి క్లిక్కుతో విభాగం మార్పు కావాలి (జావాస్క్రిప్టు)',
 'tog-showtoc' => 'విషయసూచిక చూపించు (3 కంటే ఎక్కువ శీర్షికలున్న పేజీలకు)',
 'tog-rememberpassword' => 'ఈ విహారిణిలో నా ప్రవేశాన్ని గుర్తుంచుకో (గరిష్ఠంగా $1 {{PLURAL:$1|రోజు|రోజుల}}కి)',
-'tog-watchcreations' => 'à°¨à±\87à°¨à±\81 à°¸à±\83à°·à±\8dà°\9fà°¿à°\82à°\9aà°¿à°¨ à°ªà±\87à°\9cà±\80లనà±\81 à°¨à°¾ à°µà±\80à°\95à±\8dà°·à°£ à°\9cాబితాà°\95à±\81 à°\95à°²à±\81à°ªు',
-'tog-watchdefault' => 'à°¨à±\87à°¨à±\81 à°¦à°¿à°¦à±\8dà°¦à±\81బాà°\9fà±\8dà°²à±\81 à°\9aà±\87సిన à°ªà±\87à°\9cà±\80లనà±\81 à°¨à°¾ à°µà±\80à°\95à±\8dà°·à°£ à°\9cాబితాà°\95à±\81 à°\95à°²à±\81à°ªు',
+'tog-watchcreations' => 'à°¨à±\87à°¨à±\81 à°¸à±\83à°·à±\8dà°\9fà°¿à°\82à°\9aà±\87 à°ªà±\87à°\9cà±\80లనà±\81 à°®à°°à°¿à°¯à±\81 à°¦à°¸à±\8dà°¤à±\8dరాలనà±\81 à°¨à°¾ à°µà±\80à°\95à±\8dà°·à°£ à°\9cాబితాà°\95à±\81 à°\9aà±\87à°°à±\8dà°\9aు',
+'tog-watchdefault' => 'à°¨à±\87à°¨à±\81 à°®à°¾à°°à±\8dà°\9aà±\87 à°ªà±\87à°\9cà±\80లనà±\81 à°®à°°à°¿à°¯à±\81 à°¦à°¸à±\8dà°¤à±\8dరాలనà±\81 à°¨à°¾ à°µà±\80à°\95à±\8dà°·à°£ à°\9cాబితాà°\95à±\81 à°\9aà±\87à°°à±\8dà°\9aు',
 'tog-watchmoves' => 'నేను తరలించిన పేజీలను దస్త్రాలను నా వీక్షణ జాబితాకు చేర్చు',
 'tog-watchdeletion' => 'నేను తొలగించిన పేజీలను దస్త్రాలను నా వీక్షణ జాబితాకు చేర్చు',
 'tog-minordefault' => 'ప్రత్యేకంగా తెలుపనంతవరకూ నా మార్పులను చిన్న మార్పులుగా గుర్తించు',
@@ -302,7 +302,7 @@ $messages = array(
 'vector-simplesearch-preference' => 'మెరుగైన అన్వేషణ సలహాలని చేతనంచేయి (వెక్టర్ అలంకారానికి మాత్రమే)',
 'vector-view-create' => 'సృష్టించు',
 'vector-view-edit' => 'సవరించు',
-'vector-view-history' => 'à°\9aà°°à°¿à°¤à±\8dరని చూడండి',
+'vector-view-history' => 'à°\9aà°°à°¿à°¤à±\8dà°°à°¨à±\81 చూడండి',
 'vector-view-view' => 'చదువు',
 'vector-view-viewsource' => 'మూలాన్ని చూడండి',
 'actions' => 'పనులు',
@@ -405,6 +405,10 @@ $1',
 'youhavenewmessages' => 'మీకు $1 ఉన్నాయి ($2).',
 'newmessageslink' => 'కొత్త సందేశాలు',
 'newmessagesdifflink' => 'క్రితం సంచికతో గల తేడాలు',
+'youhavenewmessagesfromusers' => 'మీకు {{PLURAL:$3|మరో వాడుకరి|$3 వాడుకరుల}} నుండి $1 ($2).',
+'youhavenewmessagesmanyusers' => 'మీకు చాలా వాడుకరుల నుండి $1 ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|ఒక కొత్త సందేశం వచ్చింది|కొత్త సందేశాలు ఉన్నాయి}}',
+'newmessagesdifflinkplural' => 'చివరి {{PLURAL:$1|మార్పు|మార్పులు}}',
 'youhavenewmessagesmulti' => '$1లో మీకో సందేశం ఉంది',
 'editsection' => 'మార్చు',
 'editold' => 'సవరించు',
@@ -526,6 +530,8 @@ $2',
 'ns-specialprotected' => 'ప్రత్యేక పేజీలపై దిద్దుబాట్లు చేయలేరు.',
 'titleprotected' => "సభ్యులు [[User:$1|$1]] ఈ పేజీని సృష్టించనివ్వకుండా నిరోదిస్తున్నారు.
 అందుకు ఇచ్చిన కారణం: ''$2''.",
+'exception-nologin' => 'లోనికి ప్రవేశించిలేరు',
+'exception-nologin-text' => 'ఈ వికీలో ఈ పేజీ లేదా పనికి మీరు తప్పనిసరిగా ప్రవేశించివుండాలి.',
 
 # Virus scanner
 'virus-badscanner' => "తప్పుడు స్వరూపణం: తెలియని వైరస్ స్కానర్: ''$1''",
@@ -547,6 +553,7 @@ $2',
 'remembermypassword' => 'ఈ కంప్యూటరులో నా ప్రవేశాన్ని గుర్తుంచుకో (గరిష్ఠంగా $1 {{PLURAL:$1|రోజు|రోజుల}}కి)',
 'securelogin-stick-https' => 'ప్రవేశం తర్వాత కూడా HTTPSకి అనుసంధానమై ఉండు',
 'yourdomainname' => 'మీ డోమైను',
+'password-change-forbidden' => 'ఈ వికీలో మీరు సంకేతపదాలను మార్చలేరు.',
 'externaldberror' => 'డేటాబేసు అధీకరణలో పొరపాటు జరిగింది లేదా మీ బయటి ఖాతాని తాజాకరించడానికి మీకు అనుమతి లేదు.',
 'login' => 'లోనికి రండి',
 'nav-login-createaccount' => 'లోనికి ప్రవేశించండి / ఖాతాని సృష్టించుకోండి',
@@ -616,6 +623,7 @@ $2',
 'invalidemailaddress' => 'మీరు ఇచ్చిన ఈ-మెయిలు చిరునామా సరైన రీతిలో లేనందున అంగీకరించటంలేదు.
 దయచేసి ఈ-మెయిలు చిరునామాను సరైన రీతిలో ఇవ్వండి లేదా ఖాళీగా వదిలేయండి.',
 'cannotchangeemail' => 'ఈ వికీలో ఖాతా ఈ-మెయిలు చిరునామాను మార్చుకోలేరు.',
+'emaildisabled' => 'ఈ సైటు ఈమెయిళ్ళను పంపించలేదు.',
 'accountcreated' => 'ఖాతాని సృష్టించాం',
 'accountcreatedtext' => '$1 కి వాడుకరి ఖాతాని సృష్టించాం.',
 'createaccount-title' => '{{SITENAME}} కోసం ఖాతా సృష్టి',
@@ -658,7 +666,7 @@ $2',
 'passwordreset-legend' => 'సంకేతపదాన్ని మార్చుకోండి',
 'passwordreset-disabled' => 'ఈ వికీలో సంకేతపదాల మార్పును అచేతనం చేసాం.',
 'passwordreset-pretext' => '{{PLURAL:$1||డేటా శకలాల్లోంచి ఒకదాన్ని ఇవ్వండి}}',
-'passwordreset-username' => 'వాడుకరిపేరు:',
+'passwordreset-username' => 'వాడుకరి పేరు:',
 'passwordreset-domain' => 'డొమైన్:',
 'passwordreset-email' => 'ఈ-మెయిలు చిరునామా:',
 'passwordreset-emailtitle' => '{{SITENAME}}లో ఖాతా వివరాలు',
@@ -801,6 +809,7 @@ $2
 'note' => "'''గమనిక:'''",
 'previewnote' => "'''ఇది మునుజూపు మాత్రమేనని గుర్తుంచుకోండి.'''
 మీ మార్పులు ఇంకా భద్రమవ్వలేదు!",
+'continue-editing' => 'దిద్దుబాటుని కొనసాగించండి',
 'previewconflict' => 'భద్రపరచిన తరువాత పై టెక్స్ట్‌ ఏరియాలోని టెక్స్టు ఇలాగ కనిపిస్తుంది.',
 'session_fail_preview' => "'''క్షమించండి! సెషను డేటా పోవడం వలన మీ మార్పులను స్వీకరించలేకపోతున్నాం.'''
 దయచేసి మళ్ళీ ప్రయత్నించండి.
@@ -873,6 +882,7 @@ $2
 'edit-no-change' => 'పాఠ్యంలో ఏమీ మార్పులు లేవు గనక, మీ మార్పుని పట్టించుకోవట్లేదు.',
 'edit-already-exists' => 'కొత్త పేజీని సృష్టించలేము.
 అది ఇప్పటికే ఉంది.',
+'defaultmessagetext' => 'అప్రమేయ సందేశపు పాఠ్యం',
 
 # Parser/template warnings
 'expensive-parserfunction-warning' => 'హెచ్చరిక: ఈ పేజీలో ఖరీదైన పార్సరు పిలుపులు చాలా ఉన్నాయి.
@@ -1065,6 +1075,7 @@ $1",
 # Diffs
 'history-title' => '"$1" యొక్క కూర్పుల చరిత్ర',
 'difference-title' => '"$1" యొక్క తిరిగిచూపుల నడుమ తేడాలు',
+'difference-title-multipage' => '"$1" మరియు "$2" పేజీల మధ్య తేడా',
 'difference-multipage' => '(పేజీల మధ్య తేడా)',
 'lineno' => 'పంక్తి $1:',
 'compareselectedversions' => 'ఎంచుకున్న సంచికలను పోల్చిచూడు',
@@ -1443,9 +1454,11 @@ $1",
 'number_of_watching_users_pageview' => '[వీక్షిస్తున్న సభ్యులు: {{PLURAL:$1|ఒక్కరు|$1}}]',
 'rc_categories' => 'ఈ వర్గాలకు పరిమితం చెయ్యి ("|" తో వేరు చెయ్యండి)',
 'rc_categories_any' => 'ఏదయినా',
+'rc-change-size-new' => 'మార్పు తర్వాత $1 {{PLURAL:$1|బైటు|బైట్లు}}',
 'newsectionsummary' => '/* $1 */ కొత్త విభాగం',
 'rc-enhanced-expand' => 'వివరాలని చూపించు (జావాస్క్రిప్ట్ అవసరం)',
 'rc-enhanced-hide' => 'వివరాలను దాచు',
+'rc-old-title' => 'మొదట "$1"గా సృష్టించారు',
 
 # Recent changes linked
 'recentchangeslinked' => 'సంబంధిత మార్పులు',
@@ -1495,6 +1508,7 @@ $1",
 'minlength1' => 'పైలు పేర్లు కనీసం ఒక్క అక్షరమైనా ఉండాలి.',
 'illegalfilename' => '"$1" అనే దస్త్రపుపేరు పేజీ శీర్షికలలో వాడకూడని అక్షరాలను కలిగివుంది.
 దస్త్రపు పేరుని మార్చి మళ్ళీ ఎక్కించడానికి ప్రయత్నించండి.',
+'filename-toolong' => 'దస్త్రపు పేరు 240 బైట్ల కంటే పొడవు ఉండకూడదు.',
 'badfilename' => 'ఫైలు పేరు "$1"కి మార్చబడినది.',
 'filetype-mime-mismatch' => 'దస్త్రపు పొడగింపు ".$1" ఆ దస్త్రం యొక్క MIME రకం ($2) తో సరిపోలలేదు.',
 'filetype-badmime' => '"$1" MIME రకం ఉన్న ఫైళ్ళను ఎగుమతికి అనుమతించం.',
@@ -1555,12 +1569,12 @@ $1",
 'uploadvirus' => 'ఈ ఫైలులో వైరస్‌ ఉంది! వివరాలు: $1',
 'uploadjava' => 'ఇదొక ZIP ఫైలు, ఇందులో ఒక Java .class ఫైలు ఉంది.
 Java ఫైళ్ళ వలన భద్రతకు తూట్లు పడే అవకాశం ఉంది కాబట్టి, వాటిని ఎక్కించడానికి అనుమతి లేదు.',
-'upload-source' => 'à°®à±\82à°² à°«à±\88à°²à±\81',
+'upload-source' => 'à°®à±\82à°² à°¦à°¸à±\8dà°¤à±\8dà°°à°\82',
 'sourcefilename' => 'మూలం ఫైలుపేరు:',
 'sourceurl' => 'మూల URL:',
 'destfilename' => 'ఉద్దేశించిన ఫైలుపేరు:',
 'upload-maxfilesize' => 'గరిష్ట ఫైలు పరిమాణం: $1',
-'upload-description' => 'à°«à±\88à°²ు వివరణ',
+'upload-description' => 'దసà±\8dà°¤à±\8dà°°à°ªు వివరణ',
 'upload-options' => 'ఎక్కింపు వికల్పాలు',
 'watchthisupload' => 'ఈ ఫైలుని గమనించు',
 'filewasdeleted' => 'ఇదే పేరుతో ఉన్న ఒక ఫైలును గతంలో అప్లోడు చేసారు, తరువాతి కాలంలో దాన్ని తొలగించారు. దాన్నీ మళ్ళీ అప్లోడు చేసే ముందు, మీరు $1 ను చూడాలి',
@@ -1589,6 +1603,7 @@ $1',
 'backend-fail-delete' => '$1 ఫైలును తొలగించలేకున్నాం.',
 'backend-fail-alreadyexists' => '$1 అనే దస్త్రం ఇప్పటికే ఉంది.',
 'backend-fail-opentemp' => 'తాత్కాలిక దస్త్రాన్ని తెరవలేకపోతున్నాం.',
+'backend-fail-closetemp' => 'తాత్కాలిక దస్త్రాన్ని మూసివేయలేకపోయాం.',
 'backend-fail-read' => '$1 దస్త్రము చదువలేకపోతిమి.',
 
 # ZipDirectoryReader
@@ -1662,7 +1677,7 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization చూడండి.',
 'listfiles_count' => 'కూర్పులు',
 
 # File description page
-'file-anchor-link' => 'à°«à±\88à°²à±\81',
+'file-anchor-link' => 'దసà±\8dà°¤à±\8dà°°à°\82',
 'filehist' => 'దస్త్రపు చరిత్ర',
 'filehist-help' => 'తేదీ/సమయం ను నొక్కి ఆ సమయాన ఫైలు ఎలా ఉండేదో చూడవచ్చు.',
 'filehist-deleteall' => 'అన్నిటినీ తొలగించు',
@@ -1882,6 +1897,7 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization చూడండి.',
 ఒక చిట్టా రకాన్ని గానీ, ఒక వాడుకరి పేరు గానీ (case-sensitive), లేదా ప్రభావిత  పుటని (ఇది కూడా case-sensitive) గానీ ఎంచుకుని సంబంధిత చిట్టాను మాత్రమే చూడవచ్చు.',
 'logempty' => 'సరిపోలిన అంశాలేమీ చిట్టాలో లేవు.',
 'log-title-wildcard' => 'ఈ పాఠ్యంతో మొదలయ్యే పుస్తకాల కొరకు వెతుకు',
+'showhideselectedlogentries' => 'ఎంచుకున్న చిట్టా పద్దులను చూపించు/దాచు',
 
 # Special:AllPages
 'allpages' => 'అన్ని పేజీలు',
@@ -1901,6 +1917,9 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization చూడండి.',
 'allpages-bad-ns' => '{{SITENAME}} లో "$1" అనే నేమ్&zwnj;స్పేస్ లేదు.',
 'allpages-hide-redirects' => 'దారిమార్పులను దాచు',
 
+# SpecialCachedPage
+'cachedspecial-refresh-now' => 'సరికొత్త కూర్పును చూడండి.',
+
 # Special:Categories
 'categories' => 'వర్గాలు',
 'categoriespagetext' => 'ఈ క్రింది {{PLURAL:$1|వర్గం పేజీలను లేదా మాధ్యమాలను కలిగివుంది|వర్గాలు పేజీలను లేదా మాధ్యమాలను కలిగివున్నాయి}}.
@@ -2100,6 +2119,8 @@ $UNWATCHURL కి వెళ్ళండి.
 'rollback' => 'దిద్దుబాట్లను రద్దుచేయి',
 'rollback_short' => 'రద్దుచేయి',
 'rollbacklink' => 'రద్దుచేయి',
+'rollbacklinkcount' => '$1 {{PLURAL:$1|మార్పును|మార్పులను}} రద్దుచేయి',
+'rollbacklinkcount-morethan' => '$1 కంటే ఎక్కువ {{PLURAL:$1|మార్పును|మార్పులను}} రద్దుచేయి',
 'rollbackfailed' => 'రద్దుచేయటం విఫలమైంది',
 'cantrollback' => 'రచనను వెనక్కి తీసుకువెళ్ళలేము; ఈ పేజీకి ఇదొక్కటే రచన.',
 'alreadyrolled' => '[[:$1]]లో [[User:$2|$2]] ([[User talk:$2|చర్చ]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) చేసిన చివరి మార్పును రద్దు చెయ్యలేము;
@@ -2147,6 +2168,7 @@ $UNWATCHURL కి వెళ్ళండి.
 'protect-level-sysop' => 'నిర్వాహకులు మాత్రమే',
 'protect-summary-cascade' => 'కాస్కేడింగు',
 'protect-expiring' => '$1 (UTC)న కాలంచెల్లుతుంది',
+'protect-expiring-local' => '$1న కాలంచెల్లుతుంది',
 'protect-expiry-indefinite' => 'నిరవధికం',
 'protect-cascade' => 'ఈ పేజీకి జతపరిచిన పేజీలను కూడా రక్షించు (కాస్కేడింగు రక్షణ)',
 'protect-cantedit' => 'ఈ పేజీ యొక్క సంరక్షణా స్థాయిని మీరు మార్చలేరు, ఎందుకంటే దాన్ని మార్చే అనుమతి మీకు లేదు.',
@@ -2537,7 +2559,7 @@ $UNWATCHURL కి వెళ్ళండి.
 # Namespace 8 related
 'allmessages' => 'అన్ని సిస్టం సందేశాలు',
 'allmessagesname' => 'పేరు',
-'allmessagesdefault' => 'à°¡à±\80ఫాలà±\8dà°\9fు పాఠ్యం',
+'allmessagesdefault' => 'à°\85à°ªà±\8dà°°à°®à±\87à°¯ à°¸à°\82à°¦à±\87శపు పాఠ్యం',
 'allmessagescurrent' => 'ప్రస్తుత పాఠ్యం',
 'allmessagestext' => 'మీడియావికీ పేరుబరిలో ఉన్న అంతరవర్తి సందేశాల జాబితా ఇది.
 సాధారణ మీడియావికీ స్థానికీకరణకి మీరు తోడ్పడాలనుకుంటే, దయచేసి [//www.mediawiki.org/wiki/Localisation మీడియావికీ స్థానికీకరణ] మరియు [//translatewiki.net ట్రాన్స్&zwnj;లేట్&zwnj;వికీ.నెట్] సైట్లను చూడండి.',
@@ -2613,6 +2635,7 @@ $UNWATCHURL కి వెళ్ళండి.
 
 # JavaScriptTest
 'javascripttest' => 'జావాస్క్రిప్ట్ పరీక్ష',
+'javascripttest-title' => '$1 పరీక్షలు నడుస్తున్నాయి',
 
 # Tooltip help for the actions
 'tooltip-pt-userpage' => 'మీ వాడుకరి పేజీ',
@@ -2973,6 +2996,7 @@ $1',
 'exif-iimcategory' => 'వర్గం',
 'exif-iimsupplementalcategory' => 'అనుషంగిక వర్గాలు',
 'exif-datetimeexpires' => 'దీని తరువాత వాడవద్దు',
+'exif-datetimereleased' => 'విడుదల తేదీ',
 'exif-identifier' => 'గుర్తింపకం',
 'exif-lens' => 'వాడిన కటకం',
 'exif-serialnumber' => 'కెమేరా యొక్క సీరియల్ నంబర్',
@@ -3370,6 +3394,9 @@ $5
 'version-software' => 'స్థాపిత మృదూపకరణాలు',
 'version-software-product' => 'ప్రోడక్టు',
 'version-software-version' => 'వెర్షను',
+'version-entrypoints' => 'ప్రవేశ బిందు చిరునామాలు',
+'version-entrypoints-header-entrypoint' => 'ప్రవేశ బిందువు',
+'version-entrypoints-header-url' => 'చిరునామా',
 
 # Special:FilePath
 'filepath' => 'పూర్తి చిరునామా',
@@ -3502,8 +3529,11 @@ $5
 
 # API errors
 'api-error-badaccess-groups' => 'ఈ వికీ లోనికి దస్త్రాలను ఎక్కించే అనుమతి మీకు లేదు.',
+'api-error-duplicate-archive-popup-title' => 'నకిలీ {{PLURAL:$1|దస్త్రాన్ని|దస్త్రాలను}} ఇప్పటికే తొలగించారు.',
+'api-error-duplicate-popup-title' => 'నకిలీ {{PLURAL:$1|దస్త్రం|దస్త్రాలు}}.',
 'api-error-empty-file' => 'మీరు దాఖలుచేసిన ఫైల్ ఖాళీది.',
 'api-error-emptypage' => 'కొత్త మరియు ఖాళీ పేజీలను సృష్టించడానికి అనుమతి లేదు.',
+'api-error-file-too-large' => 'మీరు సమర్పించిన దస్త్రం చాలా పెద్దగా ఉంది.',
 'api-error-filename-tooshort' => 'దస్త్రపు పేరు మరీ చిన్నగా ఉంది.',
 'api-error-filetype-banned' => 'ఈ రకపు దస్త్రాలని నిషేధించారు.',
 'api-error-http' => 'అంతర్గత దోషము: సేవకానికి అనుసంధానమవలేకపోతున్నది.',
@@ -3512,9 +3542,12 @@ $5
 'api-error-mustbeloggedin' => 'దస్త్రాలను ఎక్కించడానికి మీరు ప్రవేశించివుండాలి.',
 'api-error-nomodule' => 'అంతర్గత దోషము: ఎక్కింపు పర్వికము అమర్చబడలేదు.',
 'api-error-ok-but-empty' => 'అంతర్గత దోషము: సేవకము నుండి ఎటువంటి స్పందనా లేదు.',
+'api-error-stashfailed' => 'అంతర్గత పొరపాటు: తాత్కాలిక దస్త్రాన్ని భద్రపరచడంలో సేవకి విఫలమైంది.',
 'api-error-unclassified' => 'ఒక తెలియని దోషము సంభవించినది',
-'api-error-unknown-code' => 'తెలియని దోషము: $1',
+'api-error-unknown-code' => 'తెలియని పొరపాటు: "$1".',
+'api-error-unknown-error' => 'అంతర్గత పొరపాటు: మీ దస్త్రాన్ని ఎక్కించేప్పుడు ఏదో పొరపాటు జరిగింది.',
 'api-error-unknown-warning' => 'తెలియని హెచ్చరిక: $1',
+'api-error-unknownerror' => 'తెలియని పొరపాటు: "$1".',
 'api-error-uploaddisabled' => 'ఈ వికీలో ఎక్కింపులని అచేతనం చేసారు.',
 'api-error-verification-error' => 'ఈ ఫైల్ పాడైవుండవచ్చు, లేదా తప్పుడు పొడిగింతను కలిగివుండవచ్చు.',
 
index ae6b048..c035376 100644 (file)
@@ -76,7 +76,7 @@ $messages = array(
 'tog-underline' => 'Subliña ligasaun sira:',
 'tog-justify' => 'Justifika parágrafu sira',
 'tog-hideminor' => "Lá'os hatudu muda ki-ki'ik iha mudansa foufoun sira",
-'tog-usenewrc' => 'Uza lista "Mudansa foufoun sira" di\'ak liu (JavaScript)',
+'tog-usenewrc' => 'Iha lista "mudansa foufoun sira" no "lista hateke": Hatudu mudansa iha grupu sira - grupu ida ba pájina ida (presiza JavaScript)',
 'tog-showtoolbar' => 'Hatudu kaixa edita (presiza JavaScript)',
 'tog-watchcreations' => "Hateke pájina sira-ne'ebé ha'u kria",
 'tog-watchdefault' => "Hateke pájina sira-ne'ebé ha'u edita",
@@ -171,9 +171,11 @@ $messages = array(
 'faqpage' => 'Project:FAQ',
 
 # Vector skin
+'vector-action-protect' => 'Proteje',
 'vector-view-create' => 'Kria',
 'vector-view-edit' => 'Edita',
 'vector-view-history' => 'Haree istória',
+'vector-view-view' => 'Lee',
 'actions' => 'Aksaun sira',
 
 'errorpagetitle' => 'Sala',
@@ -396,11 +398,16 @@ Ita-nia mudansa la armazenadu seidauk!",
 'lineno' => 'Liña $1:',
 
 # Search results
+'searchresults' => 'Rezultadu sira',
+'searchresults-title' => 'Rezultadu sira ba buka "$1"',
 'searchsubtitleinvalid' => "Ita buka tiha ona '''$1'''",
 'prevn' => 'molok {{PLURAL:$1|$1}}',
 'nextn' => 'oinmai {{PLURAL:$1|$1}}',
+'shown-title' => 'Hatudu {{PLURAL:$1|rezultadu|rezultadu}} $1 kada pájina',
 'viewprevnext' => 'Haree ($1 {{int:pipe-separator}} $2) ($3)',
 'searchmenu-new' => "'''Kria pájina \"[[:\$1]]\" iha wiki ne'e!'''",
+'searchprofile-everything' => 'Hotu',
+'searchprofile-articles-tooltip' => 'Buka iha $1',
 'searchprofile-project-tooltip' => 'Buka iha $1',
 'search-result-size' => '$1 ({{PLURAL:$2|liafuan ida|liafuan $2}})',
 'search-section' => '(seksaun $1)',
@@ -500,6 +507,9 @@ Ita-nia mudansa la armazenadu seidauk!",
 # Recent changes
 'nchanges' => '$1 {{PLURAL:$1|diferensa|diferensa}}',
 'recentchanges' => 'Mudansa foufoun sira',
+'recentchanges-label-newpage' => 'Pájina foun',
+'recentchanges-label-minor' => "Ne'e mudansa ki'ik",
+'recentchanges-label-bot' => 'Edita husi prosesu automátiku ("bot")',
 'rcshowhideminor' => "$1 muda ki-ki'ik",
 'rcshowhidebots' => '$1 bot sira',
 'rcshowhideliu' => '$1 ema rejista',
@@ -809,6 +819,7 @@ Ita-nia mudansa la armazenadu seidauk!",
 'tooltip-ca-unwatch' => 'Hasai pájina ne\'e husi Ita-nia "lista hateke"',
 'tooltip-search' => 'Buka iha {{SITENAME}}',
 'tooltip-search-go' => "Bá pájina ho naran ne'e (se iha)",
+'tooltip-search-fulltext' => "Buka pájina sira-ne'ebé iha testu ne'e iha laran",
 'tooltip-p-logo' => 'Pájina Mahuluk',
 'tooltip-n-mainpage' => 'Vizita Pájina Mahuluk',
 'tooltip-n-mainpage-description' => 'Vizita Pájina Mahuluk',
@@ -852,6 +863,13 @@ Ita-nia mudansa la armazenadu seidauk!",
 'showhidebots' => '($1 bot sira)',
 'ilsubmit' => 'Buka',
 
+# Bad image list
+'bad_image_list' => "Formatu:
+
+Liña hotu tenke komesa ho *
+Ligasaun uluk iha liña tenke ligasaun bá imajen aat.
+Ligasaun seluk iha liña - ne'e pájina sira iha ne'ebé bele inklui imajen aat.",
+
 # EXIF tags
 'exif-make' => 'Fabrikante kámara nian',
 'exif-model' => 'Kámara',
index 96b82c8..f325721 100644 (file)
@@ -405,6 +405,10 @@ Tingnan ang [[Special:Version|pahina ng bersiyon]].',
 'youhavenewmessages' => 'Mayroon kang $1 ($2).',
 'newmessageslink' => 'mga bagong mensahe',
 'newmessagesdifflink' => 'huling pagbabago',
+'youhavenewmessagesfromusers' => 'Mayroon kang $1 magmula sa {{PLURAL:$3|ibang tagagamit|$3 mga tagagamit}} ($2).',
+'youhavenewmessagesmanyusers' => 'Mayroon kang $1 magmula sa maraming mga tagagamit ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|isang bagong mensahe|bagong mga mensahe}}',
+'newmessagesdifflinkplural' => 'huling {{PLURAL:$1|pagbabago|mga pagbabago}}',
 'youhavenewmessagesmulti' => 'Mayroon kang mga bagong mensahe sa $1',
 'editsection' => 'baguhin',
 'editsection-brackets' => '[$1]',
@@ -956,6 +960,7 @@ Hindi isasama ang ilang mga suleras.',
 'expansion-depth-exceeded-warning' => 'Lumampas ang pahina sa lalim ng paglawak',
 'parser-unstrip-loop-warning' => 'Napansin ang silo ng hindi pagtalop',
 'parser-unstrip-recursion-limit' => 'Nalampasan ang hangganan ng rekursiyon ng hindi pagtalop ($1)',
+'converter-manual-rule-error' => 'Napansin ang kamalian sa alituntunin ng kinakamay na pagpapalit ng wika',
 
 # "Undo" feature
 'undo-success' => 'Matatanggal ang pagbabago.
index 54ae656..01f0009 100644 (file)
@@ -35,6 +35,7 @@
  * @author Mirzali
  * @author Mskyrider
  * @author Myildirim2007
+ * @author Nazif İLBEK
  * @author Reedy
  * @author Runningfridgesrule
  * @author Sadrettin
@@ -326,58 +327,58 @@ $messages = array(
 'tog-hideminor' => 'Son değişiklikler sayfasında küçük değişiklikleri gizle',
 'tog-hidepatrolled' => 'Son değişikliklerde gözden geçirilen düzenlemeleri gizle',
 'tog-newpageshidepatrolled' => 'Kontrol edilmiş sayfaları yeni sayfalar listesinde gizle',
-'tog-extendwatchlist' => 'İzleme listesini sadece son değil, tüm değişiklikleri görmek için genişlet',
+'tog-extendwatchlist' => 'İzleme listesini sadece en son değil, tüm değişiklikleri göstermek için genişlet',
 'tog-usenewrc' => 'Son değişiklikler sayfasındaki ve izleme listesindeki değişiklikleri gruplandırma (JavaScript gerektirir)',
 'tog-numberheadings' => 'Başlıkları otomatik numaralandır',
-'tog-showtoolbar' => 'Düzenleme yaparken araç çubuğunu göster (JavaScript gerekir)',
-'tog-editondblclick' => 'Çift tıklayarak sayfayı düzenle (JavaScript gerekir)',
-'tog-editsection' => 'Bölümleri [değiştir] bağlantıları ile düzenlemeyi etkinleştir',
-'tog-editsectiononrightclick' => 'Bölümleri bölüm başlığına sağ tıklayarak değiştirebilme olanağı ver (JavaScript)',
+'tog-showtoolbar' => 'Düzenleme yaparken araç çubuğunu göster (JavaScript gerektirir)',
+'tog-editondblclick' => 'Çift tıklayarak sayfaları düzenle (JavaScript gerektirir)',
+'tog-editsection' => 'Bölümleri [{{int:Editsection}}] bağlantıları ile düzenlemeyi etkinleştir',
+'tog-editsectiononrightclick' => 'Bölüm başlığına sağ tıklayarak bölümleri düzenleyebilme olanağı ver (JavaScript gerektirir)',
 'tog-showtoc' => 'İçindekiler tablosunu göster (3 taneden fazla başlığı olan sayfalar için)',
 'tog-rememberpassword' => 'Girişimi bu tarayıcıda hatırla (en fazla $1 {{PLURAL:$1|gün|gün}} için)',
 'tog-watchcreations' => 'Açtığım sayfaları ve yüklediğim dosyaları izleme listeme ekle',
 'tog-watchdefault' => 'Düzenleme yaptığım sayfaları ve dosyaları izleme listeme ekle',
 'tog-watchmoves' => 'Taşıdığım sayfaları ve dosyaları izleme listeme ekle',
 'tog-watchdeletion' => 'Sildiğim sayfaları ve dosyaları izleme listeme ekle',
-'tog-minordefault' => "Değişikliği 'küçük değişiklik' olarak seçili getir",
-'tog-previewontop' => 'Önizlemeyi yazma alanın üstünde göster',
-'tog-previewonfirst' => 'Değiştirmede önizlemeyi göster',
-'tog-nocache' => 'Tarayıcı sayfalarını bellekleme',
+'tog-minordefault' => 'Varsayılan olarak bütün düzenlemeleri küçük olarak işaretle',
+'tog-previewontop' => 'Ön izlemeyi düzenleme kutusunun üstünde göster',
+'tog-previewonfirst' => 'İlk düzenlemede ön izlemeyi göster',
+'tog-nocache' => 'Tarayıcı sayfalarını önbelleğe almayı devre dışı bırak',
 'tog-enotifwatchlistpages' => 'İzleme listemdeki bir sayfanın ya da dosyanın değiştirilmesi durumunda bana e-posta gönder',
-'tog-enotifusertalkpages' => 'Kullanıcı sayfamda değişiklik olduğunda bana e-posta gönder',
+'tog-enotifusertalkpages' => 'Kullanıcı mesaj sayfamda değişiklik olduğunda bana e-posta gönder',
 'tog-enotifminoredits' => 'Sayfalardaki ve dosyalardaki küçük değişikliklerde bana e-posta gönder',
 'tog-enotifrevealaddr' => 'E-posta adresimi bildiri postalarımda göster.',
 'tog-shownumberswatching' => 'İzleyen kullanıcı sayısını göster',
 'tog-oldsig' => 'Mevcut imza:',
 'tog-fancysig' => 'İmzaya vikimetin muamelesi yap (otomatik bir bağlantı olmadan)',
-'tog-externaleditor' => 'Harici düzenleyici kullan (deneyimli kullanıcılar içindir; bilgisayarınızda özel ayarlar gerektirir. [detaylı bilgi için : //www.mediawiki.org/wiki/Manual:External_editors])',
-'tog-externaldiff' => 'Harici karşılaştırıcı kullan (deneyimli kullanıcılar içindir; bilgisayarınızda özel ayarlar gerektirir. [detaylı bilgi için : //www.mediawiki.org/wiki/Manual:External_editors])',
-'tog-showjumplinks' => '"Git" bağlantısı etkinleştir',
-'tog-uselivepreview' => 'Canlı önizleme özelliğini kullan (JavaScript) (daha deneme aşamasında)',
+'tog-externaleditor' => 'Varsayılan olarak harici düzenleyici kullan (deneyimli kullanıcılar içindir ve bilgisayarınızda özel ayarlar gerektirir. [//www.mediawiki.org/wiki/Manual:External_editors Ayrıntılı bilgi için tıklayın.])',
+'tog-externaldiff' => 'Varsayılan olarak harici karşılaştırıcı kullan (deneyimli kullanıcılar içindir ve bilgisayarınızda özel ayarlar gerektirir. [//www.mediawiki.org/wiki/Manual:External_editors Ayrıntılı bilgi için tıklayın.])',
+'tog-showjumplinks' => '"{{int:jumpto}}" erişilebilirlik bağlantısı etkinleştir',
+'tog-uselivepreview' => 'Canlı ön izlemeyi kullan (JavaScript gerektirir ve özellik deneme aşamasındadır)',
 'tog-forceeditsummary' => 'Özeti boş bıraktığımda beni uyar',
-'tog-watchlisthideown' => 'İzleme listemden benim değişikliklerimi gizle',
+'tog-watchlisthideown' => 'İzleme listemden düzenlemelerimi gizle',
 'tog-watchlisthidebots' => 'İzleme listemden bot değişikliklerini gizle',
 'tog-watchlisthideminor' => 'İzleme listemden küçük değişiklikleri gizle',
 'tog-watchlisthideliu' => 'İzleme listemde, kayıtlı kullanıcılar tarafından yapılan değişiklikleri gösterme',
-'tog-watchlisthideanons' => 'İzleme listemde, anonim kullanıcılar tarafından yapılan değişiklikleri gösterme',
-'tog-watchlisthidepatrolled' => 'İzleme listesinde gözlenmiş değişiklikleri gizle',
+'tog-watchlisthideanons' => 'İzleme listemde, anonim kullanıcılar tarafından yapılan değişiklikleri gizle',
+'tog-watchlisthidepatrolled' => 'İzleme listesinde kontrol edilmiş değişiklikleri gizle',
 'tog-nolangconversion' => 'Varyant dönüştürmesini devre dışı bırak',
 'tog-ccmeonemails' => 'Diğer kullanıcılara gönderdiğim e-postaların kopyalarını bana da gönder',
-'tog-diffonly' => 'Sayfa içeriğini sürüm farklarının aşağısında gösterme',
+'tog-diffonly' => 'Sayfa içeriğini sürüm farklarının altında gösterme',
 'tog-showhiddencats' => 'Gizli kategorileri göster',
 'tog-noconvertlink' => 'Bağlantı başlığı dönüştürmesini devre dışı bırakma',
-'tog-norollbackdiff' => 'Rollback uygulandıktan sonra değişikliği sil',
+'tog-norollbackdiff' => 'Geridönüş uygulandıktan sonra değişikliği atla',
 
 'underline-always' => 'Daima',
 'underline-never' => 'Asla',
-'underline-default' => 'Tarayıcı karar versin',
+'underline-default' => 'Tarayıcı varsayılanı',
 
 # Font style option in Special:Preferences
-'editfont-style' => 'Değişiklik alanı yazı tipi biçemi:',
+'editfont-style' => 'Düzenleme alanının yazı tipi:',
 'editfont-default' => 'Tarayıcı varsayılanı',
-'editfont-monospace' => 'Sabit yer kaplayan yazı tipi',
-'editfont-sansserif' => 'Sans-serif yazı tipi',
-'editfont-serif' => 'Serif yazı tipi',
+'editfont-monospace' => 'Sabit aralıklı yazı tipi',
+'editfont-sansserif' => 'Çıkıntısız (Sans-serif) yazı tipi',
+'editfont-serif' => 'Çıkıntılı (serif) yazı tipi',
 
 # Dates
 'sunday' => 'Pazar',
@@ -682,8 +683,8 @@ Başka bir kullanıcı tarafından silinmiş olabilir.',
 Hiçbir açıklama yapılmadı.',
 'badtitle' => 'Geçersiz başlık',
 'badtitletext' => 'Girilen sayfa adı ya hatalı ya boş ya da diller arası bağlantı veya vikiler arası bağlantı içerdiğinden geçerli değil. Başlıklarda kullanılması yasak olan bir ya da daha çok karakter içeriyor olabilir.',
-'perfcached' => 'Veriler daha önceden hazırlanmış olabilir. Bu sebeple güncel olmayabilir! A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.',
-'perfcachedts' => 'Aşağıda saklanmış bilgiler bulunmaktadır, son güncelleme tarihi: $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.',
+'perfcached' => 'Aşağıdaki veriler önbellekten alınmıştır ve güncel olmayabilir. Önbellekte en fazla {{PLURAL:$1|bir sonuç|$1 sonuç}} mevcut.',
+'perfcachedts' => 'Aşağıdaki veri önbelleklenmiştir, son güncelleme tarihi: $1. Önbellekte en fazla {{PLURAL:$1|bir sonuç|$4 sonuç}} mevcut.',
 'querypage-no-updates' => 'Şu an için güncellemeler devre dışı bırakıldı. Buradaki veri hemen yenilenmeyecektir.',
 'wrong_wfQuery_params' => 'wfQuery() ye yanlış parametre<br />
 Fonksiyon: $1<br />
@@ -707,6 +708,9 @@ $2',
 'ns-specialprotected' => '{{ns:special}} alanadı içindeki sayfalar değiştirilemez.',
 'titleprotected' => "[[User:$1|$1]] tarafından oluşturulması engellenmesi için bu sayfa koruma altına alınmıştır.
 Verilen sebep: ''$2''.",
+'invalidtitle-knownnamespace' => '"$2" alan adı için "$3" metni geçersiz bir başlık',
+'exception-nologin' => 'Giriş yapılmamış',
+'exception-nologin-text' => 'Bu sayfa ya da eylem için bu vikide oturum açmış olmanız gerekir.',
 
 # Virus scanner
 'virus-badscanner' => "Yanlış ayarlama: bilinmeyen virüs tarayıcı: ''$1''",
@@ -728,6 +732,7 @@ Hesabınız açıldı.
 'remembermypassword' => 'Girişimi bu tarayıcıda hatırla (en fazla $1 {{PLURAL:$1|gün|gün}} için)',
 'securelogin-stick-https' => "Giriş yaptıktan sonra HTTPS'e bağlı kal",
 'yourdomainname' => 'Alan adınız:',
+'password-change-forbidden' => 'Bu vikide parolanızı değiştiremezsiniz.',
 'externaldberror' => 'Ya doğrulama veritabanı hatası var ya da kullanıcı hesabınızı güncellemeye yetkiniz yok.',
 'login' => 'Oturum aç',
 'nav-login-createaccount' => 'Oturum aç / hesap oluştur',
@@ -1025,8 +1030,8 @@ Yaptığınız katkının başka katılımcılar tarafından acımasızca deği
 Ayrıca buraya katkıda bulunarak, bu katkının kendiniz tarafından yazıldığına ya da kamuya açık bir kaynaktan ya da başka bir özgür/ücretsiz kaynaktan kopyalandığına güvence vermiş oluyorsunuz. '''Buraya, telif sahibinin izni olmadan telif hakkı ile korunan eserleri eklemeyiz! '''",
 'copyrightwarning2' => 'Lütfen, {{SITENAME}} sitesine bulunacağınız tüm katkıların diğer üyeler tarafından düzenlenebileceğini, değiştirilebileceğini ya da silinebileceğini hatırlayın. Yazılarınızın merhametsizce değiştirilebilmesine rıza göstermiyorsanız buraya katkıda bulunmayın. <br />
 Ayrıca bu ekleyeceğiniz yazıyı sizin yazdığınızı ya da serbest kopyalama izni veren bir kaynaktan kopyaladığınızı bize taahhüt etmektesiniz (ayrıntılar için referans: $1).',
-'longpageerror' => "'''HATA: Girdiğiniz metnin uzunluğu {{PLURAL:$1|bir kilobayt|$1 kilobayt}}, ve en fazla uzunluktan {{PLURAL:$2|bir kilobayt|$2 kilobayt}} daha fazladır.
-Kaydedilmesi mümkün değildir.'''",
+'longpageerror' => "'''Hata: Girdiğiniz metnin uzunluğu kabul edilebilir en fazla uzunluk olan {{PLURAL:$2|bir kilobayt|$2 kilobayt}}tan fazladır ve {{PLURAL:$1|bir kilobayt|$1 kilobayt}} büyüklüğündedir.'''
+Değişikliğiniz kaydedilemez.",
 'readonlywarning' => "'''DİKKAT: Bakım nedeni ile veritabanı şu anda kilitlidir. Bu sebeple değişiklikleriniz şu anda kaydedilememektedir. Yazdıklarınızı başka bir editöre alıp saklayabilir ve daha sonra tekrar buraya getirip kaydedebilirsiniz'''
 
 Kilitleyen hizmetli şu açıklamayı eklemiştir: $1",
index 2423ec2..d963c42 100644 (file)
@@ -41,7 +41,7 @@ $messages = array(
 'tog-hidepatrolled' => 'يېقىنقى ئۆزگەرتىشتە كۆزەتكەن تەھرىرنى يوشۇر',
 'tog-newpageshidepatrolled' => 'يېڭى بەت تىزىملىكىدە كۆزەتكەن تەھرىرنى يوشۇر',
 'tog-extendwatchlist' => 'كۈچەيتىلگەن كۆزەت تىزىملىكىدە يېقىنقى ئۆزگەرتىشنىلا كۆرسەتمەي بەلكى ھەممە ئۆزگەرتىشنى كۆرسەت',
-'tog-usenewrc' => 'كۈچەيتىلگەن يېقىنقى ئۆزگەرتىشنى قوزغات (JavaScript زۆرۈر)',
+'tog-usenewrc' => 'بەت گۇرۇپپىلىنىشىغا ئاساسەن يېقىنقى ئۆزگەرتىش ۋە كۆزەت تىزىمى (JavaScript زۆرۈر)',
 'tog-numberheadings' => 'ماۋزۇغا ئۆزلۈكىدىن تەرتىپ نومۇرى قوش',
 'tog-showtoolbar' => 'تەھرىر قورال ستونىنى كۆرسەت (JavaScript زۆرۈر)',
 'tog-editondblclick' => 'قوش چەككەندە بەت تەھرىرلە (JavaScript زۆرۈر)',
@@ -49,20 +49,20 @@ $messages = array(
 'tog-editsectiononrightclick' => 'ماۋزۇنى چاشقىنەكتە ئوڭ چېكىپ ئابزاس تەھرىرلەشكە يول قوي (JavaScript زۆرۈر)',
 'tog-showtoc' => 'مەزمۇن جەدۋىلى كۆرسەت (بىر بەتتە 3 تىن ئارتۇق ماۋزۇ بار بەتكە قارىتىلغان)',
 'tog-rememberpassword' => 'بۇ كومپيۇتېردا كىرگىنىمنى ئەستە ساقلا(ئەڭ ئۇزۇن بولغاندا $1 {{PLURAL:$1|كۈن|كۈن}})',
-'tog-watchcreations' => 'مەن قۇرغان بەتنى كۆزەت تىزىملىكىمگە قوش',
-'tog-watchdefault' => 'مەن تەھرىرلىگەن بەتنى كۆزەت تىزىملىكىمگە قوش',
-'tog-watchmoves' => 'مەن يۆتكىگەن بەتنى كۆزەت تىزىملىكىمگە قوش',
-'tog-watchdeletion' => 'مەن ئۆچۈرگەن بەتنى كۆزەت تىزىملىكىمگە قوش',
+'tog-watchcreations' => 'مەن قۇرغان بەت ۋە يۈكلىگەن ھۆججەتلەرنى كۆزەت تىزىملىكىمگە قوش',
+'tog-watchdefault' => 'مەن تەھرىرلىگەن بەت ۋە ھۆججەتنى كۆزەت تىزىملىكىمگە قوش',
+'tog-watchmoves' => 'مەن يۆتكىگەن بەت ۋە ھۆججەتنى كۆزەت تىزىملىكىمگە قوش',
+'tog-watchdeletion' => 'مەن ئۆچۈرگەن بەت ۋە ھۆججەتنى كۆزەت تىزىملىكىمگە قوش',
 'tog-minordefault' => 'ھەممە تەھرىرلەشنى ئازراقلا تەھرىرگە تەڭشە',
 'tog-previewontop' => 'تەھرىر رامكىسىنىڭ ئۈستىدە ئالدىن كۆزىتىشنى كۆرسەت',
 'tog-previewonfirst' => 'تۇنجى قېتىم تەھرىرلىگەندە ئالدىن كۆزىتىشنى كۆرسەت',
 'tog-nocache' => 'توركۆرگۈ بەت غەملەشنى چەكلە',
-'tog-enotifwatchlistpages' => 'كۆزەت تىزىملىكىمدىكى بەت ئۆزگەرگەندە ئېلخەت يوللا',
+'tog-enotifwatchlistpages' => 'كۆزەت تىزىملىكىمدىكى بەت ۋە ھۆججەت ئۆزگەرگەندە ئېلخەت يوللا',
 'tog-enotifusertalkpages' => 'مۇنازىرە بېتىم ئۆزگەرگەندە ئېلخەت يوللا',
-'tog-enotifminoredits' => 'بەت ئازراقلا تەھرىرلەنگەندىمۇ ئېلخەت يوللا',
+'tog-enotifminoredits' => 'بەت ۋە ھۆججەت ئازراقلا تەھرىرلەنگەندىمۇ ئېلخەت يوللا',
 'tog-enotifrevealaddr' => 'ئۇقتۇرۇش ئېلخەت تىزىملىكىدە ئېلخەت ئادرېسىمنى ئاشكارىلا',
 'tog-shownumberswatching' => 'بۇ بەتنى كۆزىتىۋاتقان ئىشلەتكۈچى سانىنى كۆرسەت',
-'tog-oldsig' => 'Ù\86Û\86Û\8bÛ\95تتÙ\89Ù\83Ù\89 Ø¦Ù\89Ù\85زاÙ\86Ù\89 Ø¦Ø§Ù\84دÙ\89Ù\86 Ù\83Û\86زÛ\95ت:',
+'tog-oldsig' => 'Ù\85Û\95Û\8bجÛ\87ت Ø¦Ù\89Ù\85زا:',
 'tog-fancysig' => 'ئىمزاغا wiki تېكستى سۈپىتىدە مۇئامىلە قىل (ئۆزلۈكىدىن ئۇلانما ھاسىل بولمايدۇ)',
 'tog-externaleditor' => 'كۆڭۈلدىكى ئەھۋالدا سىرتقى تەھرىرلىگۈچ ئىشلىتىدۇ (ئالىي ئىشلەتكۈچىگە تەمىنلىنىدۇ، كومپيۇتېرىڭىزدا بىر قىسىم ئالاھىدە تەڭشەش ئېلىپ بېرىشىڭىز لازىم
 [//www.mediawiki.org/wiki/Manual:External_editors تېخىمۇ كۆپ ئۇچۇر.])',
@@ -711,12 +711,12 @@ $2
 'userpage-userdoesnotexist-view' => '"$1" ئىشلەتكۈچى ھېساباتى خەتلەتمىگەن.',
 'blocked-notice-logextract' => 'بۇ ئىشلەتكۈچى نۆۋەتتە چەكلەنگەن.
 پايدىلىنىش ئۈچۈن يېقىنقى تۆۋەندە چەكلەش خاتىرە تۈرلىرى تەمىنلەندى:',
-'clearyourcache' => "'''دىققەت - ساقلىغاندىن كېيىن، تور كۆرگۈنىڭ غەملىكىنى تازىلىغاندىن كېيىنلا ئاندىن ئېلىپ بارغان ئۆزگەرتىشنى كۆرەلەيسىز.'''
+'clearyourcache' => "'''دىققەت''' - ساقلىغاندىن كېيىن، تور كۆرگۈنىڭ غەملىكىنى تازىلىغاندىن كېيىنلا ئاندىن ئېلىپ بارغان ئۆزگەرتىشنى كۆرەلەيسىز.
 * '''Mozilla / Firefox / Safari:''' دا ''Shift'' كۇنۇپكىسىنى بېسىپ تۇرۇپ ''قايتا يۈكلە''نى ياكى ''Ctrl-F5'' ياكى ''Ctrl-R'' (''Mac تا Command-R'')؛
 * '''Google Chrome:''' دا ''Ctrl-Shift-R'' (''Command-Shift-R''  Mac)
 *'''Internet Explorer:''' دا ''Ctrl'' نى بېسىپ تۇرۇپ ''يېڭىلا,'' ياكى ''Ctrl-F5''؛
 * '''Konqueror: دا ''' ''قايتا يۈكلە'' ياكى ''F5''؛
-* '''Opera:''' دا ''قورال → مايىللىق''؛ نى بېسىڭ.",
+* '''Opera:''' دا ''قورال → مايىللىق''؛ نى بېسىپ غەملەكنى تازىلاڭ.",
 'usercssyoucanpreview' => "ئەسكەرتىش:''' ساقلاشتىن ئىلگىرى  \"{{int:showpreview}}\" توپچىنى ئىشلىتىپ يېڭى CSS نى سىناڭ.",
 'userjsyoucanpreview' => "ئەسكەرتىش:''' ساقلاشتىن ئىلگىرى  \"{{int:showpreview}}\" توپچىنى ئىشلىتىپ يېڭى JS نى سىناڭ.",
 'usercsspreview' => "'''دىققەت سىز پەقەت ئۆزىڭىزنىڭ شەخسىي CSS نى ئالدىن كۆزىتىۋاتىسىز.'''
@@ -732,7 +732,7 @@ $2
 'updated' => '(يېڭىلاندى)',
 'note' => "'''ئىزاھات:'''",
 'previewnote' => "'''ئېسىڭىزدە بولسۇنكى بۇ پەقەتلا ئالدىن كۆزىتىش.'''
-ئۆزگەرتكەن مەزمۇنىڭىز تېخى ساقلانمىدى!",
+ئۆزگەرتكەنلىرىڭىز تېخى ساقلانمىدى!",
 'previewconflict' => 'بۇ ئالدىن كۆزىتىشتە ئۈستىدىكى تېكست تەھرىرلەش رايونىدىكى مەزمۇننى كۆرسەتتى. ئۇ ساقلانى تاللىغاندىن كېيىن كۆرۈنىدۇ.',
 'session_fail_preview' => "'''كەچۈرۈڭ! سىزنىڭ جەريان سانلىق مەلۇماتىڭىز يوقاپ كەتكەندە ئېلىپ بارغان تەھرىرىڭىزنى بىر تەرەپ قىلالمايمىز.'''
 قايتا سىناڭ.
@@ -885,7 +885,7 @@ $3 تەمىنلىگەن چەكلەش سەۋەبى ''$2''",
 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ئۆچۈرۈش خاتىرىسى]دىن تەپسىلىي ئۇچۇرنى تاپقىلى بولىدۇ.",
 'rev-deleted-text-unhide' => "بۇ بەتنىڭ تۈزىتىلگەن نەشرى '''ئۆچۈرۈلگەن'''.
 [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} ئۆچۈرۈش خاتىرىسى]دىن تەپسىلىي ئۇچۇرنى تاپقىلى بولىدۇ.
¨Ø§Ø´Ù\82Û\87رغÛ\87Ú\86Ù\89 Ø¨Ù\88Ù\84Û\87Ø´ Ø³Û\88Ù¾Ù\89تÙ\89دÛ\95Ø\8c Ø¯Ø§Û\8bاÙ\85Ù\84اشتÛ\87رÙ\85اÙ\82Ú\86Ù\89 Ø¨Ù\88Ù\84سÙ\89Ú­Ù\89ز Ù\8aÛ\95Ù\86Ù\89Ù\84ا [$1 Ø¨Û\87 ØªÛ\88زÙ\89تÙ\89Ù\84Ú¯Û\95Ù\86 نەشرىنى كۆرسەت]ەلەيسىز.",
¯Ø§Û\8bاÙ\85Ù\84اشتÛ\87رÙ\85اÙ\82Ú\86Ù\89 Ø¨Ù\88Ù\84سÙ\89Ú­Ù\89ز Ù\8aÛ\95Ù\86Ù\89Ù\84ا [$1 Ø¨Û\87 نەشرىنى كۆرسەت]ەلەيسىز.",
 'rev-suppressed-text-unhide' => "بۇ بەتنىڭ تۈزىتىلگەن نەشرى '''يوقىتىلغان'''.
 [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} يوقىتىش خاتىرىسى]دىن تەپسىلىي ئۇچۇرنى تاپقىلى بولىدۇ.باشقۇرغۇچى بولۇش سۈپىتىدە، داۋاملاشتۇرماقچى بولسىڭىز يەنىلا [$1 بۇ تۈزىتىلگەن نەشرىنى كۆرسەت]ەلەيسىز.",
 'rev-deleted-text-view' => "بۇ بەتنىڭ تۈزىتىلگەن نەشرى '''ئۆچۈرۈلگەن'''.
@@ -1004,7 +1004,7 @@ $1",
 'mergelogpagetext' => 'تۆۋەندىكىسى يېقىندا بىر بەتنىڭ تۈزىتىش تارىخىنىڭ باشقا بىر بەتكە بىرلەشتۈرۈلگەنلىك تىزىملىكى',
 
 # Diffs
-'history-title' => '"$1" نىڭ ئۆزگەرتىش خاتىرىسى',
+'history-title' => '"$1" نىڭ ئۆزگەرتىش نەشر تارىخى',
 'difference-multipage' => '(بەتلەر ئارىسىدىكى پەرق)',
 'lineno' => '$1 -قۇر:',
 'compareselectedversions' => 'تاللانغان نەشرىنى سېلىشتۇر',
@@ -1171,6 +1171,7 @@ $1",
 'prefs-registration' => 'خەتلەتكەن ۋاقىت:',
 'yourrealname' => 'ﺗﻮﻟﯘﻕ ئىسىم:',
 'yourlanguage' => 'تىل:',
+'yourvariant' => 'مەزمۇن تىل شالغۇتى:',
 'yournick' => 'ئىمزا:',
 'prefs-help-signature' => 'مۇنازىرە بەتتە "<nowiki>~~~~</nowiki>" ئىمزا ئىشلىتىلسە ئۇ ئۆزلۈكىدىن ئىمزايىڭىزغا ئۆزگىرىپ ۋاقىت تامغا قوشۇلىدۇ.',
 'badsig' => 'ئەسلى ئىمزا خاتا.
@@ -1244,7 +1245,7 @@ HTML بەلگىسىنى تەكشۈرۈڭ.',
 'group-suppress' => 'نازارەتچىلەر',
 'group-all' => '(ھەممىسى)',
 
-'group-user-member' => 'ئىشلەتكۈچى',
+'group-user-member' => '{{GENDER:$1|ئىشلەتكۈچى}}',
 'group-autoconfirmed-member' => 'ئۆزلۈكىدىن جەزملەنگەن ئىشلەتكۈچى',
 'group-bot-member' => 'ماشىنا ئادەم',
 'group-sysop-member' => 'باشقۇرغۇچى',
@@ -1637,7 +1638,7 @@ URL نىڭ توغرىلىقى ۋە تور بېكەتنى زىيارەت قىلى
 'filehist-filesize' => 'ھۆججەت چوڭلۇقى',
 'filehist-comment' => 'ئىزاھات',
 'filehist-missing' => 'ھۆججەت يوقالغان',
-'imagelinks' => 'ھۆججەت ئۇلىنىشى',
+'imagelinks' => 'ھۆججەت ئىشلىتىلىشى',
 'linkstoimage' => 'تۆۋەندىكى {{PLURAL:$1|بەت|$1 بەت}}  بۇ ھۆججەتكە ئۇلانغان:',
 'linkstoimage-more' => '{{PLURAL:$1|دىن كۆپ بەت ئۇلانما|دىن كۆپ بەت ئۇلانما}}  بۇ ھۆججەتكە ئۇلانغان.
 تۆۋەندىكى تىزىملىك پەقەت بۇ ھۆججەتنىڭ ئەڭ بېشىدىكى {{PLURAL:$1|بەت| $1 بەت}} ئۇلىنىشىنىلا كۆرسىتىدۇ.
@@ -1807,7 +1808,7 @@ URL نىڭ توغرىلىقى ۋە تور بېكەتنى زىيارەت قىلى
 'listusers-editsonly' => 'تەھرىرلەنگەن ئىشلەتكۈچىلەرنىلا كۆرسەت',
 'listusers-creationsort' => 'قۇرۇلغان ۋاقتى بويىچە تەرتىپلە',
 'usereditcount' => '$1 {{PLURAL:$1|قېتىم|قېتىم}} تەھرىرلەنگەن',
-'usercreated' => ' $1  $2 قۇرۇلغان',
+'usercreated' => '$1 دا $2 دە {{GENDER:$3|قۇرغان}}',
 'newpages' => 'يېڭى بەتلەر',
 'newpages-username' => 'ئىشلەتكۇچى ئىسمى:',
 'ancientpages' => 'ئەڭ كونا بەتلەر',
index 91dcd59..9de9bda 100644 (file)
@@ -54,13 +54,13 @@ $linkTrail = '/^([a-zʻʼ“»]+)(.*)$/sDu';
 $messages = array(
 # User preference toggles
 'tog-hideminor' => 'Yangi oʻzgarishlardagi kichik tahrirlani yashir',
-'tog-rememberpassword' => 'Hisob ma’lumotlarini ushbu kompyuterda eslab qolish (eng ko‘pi bilan $1 {{PLURAL:$1|kun|kun}} uchun)',
+'tog-rememberpassword' => 'Hisob ma’lumotlarini ushbu kompyuterda eslab qolish (eng ko‘pi bilan $1 {{PLURAL:$1|kunga|kunga}})',
 'tog-watchcreations' => 'Men yaratgan sahifalarni va yuklagan fayllarni kuzatuv roʻyxatimga qoʻsh',
 'tog-watchdefault' => 'Men tahrirlagan sahifa va fayllarni kuzatuv roʻyxatimga qoʻsh',
 'tog-watchmoves' => 'Men koʻchirgan sahifa va fayllarni kuzatuv roʻyxatimga qoʻsh',
 'tog-watchdeletion' => 'Men yoʻqotgan sahifa va fayllarni kuzatuv roʻyxatimga qoʻsh',
-'tog-enotifwatchlistpages' => 'Kuzatuv roʻyxatimdagi sahifa yoki fayllar oʻzgartirilsa, elektron pochtamga xat yuborilsin',
-'tog-enotifusertalkpages' => 'Munozara sahifam oʻzgartirilsa elektron pochtamga bu haqda xat yuborilsin',
+'tog-enotifwatchlistpages' => 'Kuzatuv roʻyxatimdagi sahifa yoki fayllar oʻzgartirilsa, e-pochtamga bu haqda xat yuborilsin',
+'tog-enotifusertalkpages' => 'Munozara sahifam oʻzgartirilsa, e-pochtamga bu haqda xat yuborilsin',
 'tog-oldsig' => 'Mavjud imzo:',
 'tog-fancysig' => 'Imzoni wikimatn sifatida qara (avtomatik ishoratsiz)',
 'tog-ccmeonemails' => 'Men boshqa foydalanuvchilarga yuborayotgan xatnig nusxasi oʻzimning e-pochtamga ham yuborilsin',
@@ -301,10 +301,13 @@ Siz yangi hisob yaratdingiz.
 Xatosiz yozishga urinib koʻring.',
 'wrongpassword' => 'Kiritgan mahfiy soʻzingiz notoʻgʻri. Iltimos, qaytadan kiritib koʻring.',
 'mailmypassword' => 'Yangi parolni e-mail qil',
+'emailauthenticated' => 'Sizning e-mail manzilingiz $2, $3 da tasdiqlangan.',
 'loginlanguagelabel' => 'Til: $1',
 
 # Change password dialog
+'resetpass' => 'Maxfiy soʻzni oʻzgartirish',
 'retypenew' => 'Yangi mahfiy soʻzni qayta tering:',
+'resetpass-submit-loggedin' => 'Maxfiy soʻzni oʻzgartirish',
 
 # Edit page toolbar
 'bold_sample' => 'Qalin matn',
@@ -451,6 +454,7 @@ Bu yerda: (joriy) = hozirgi koʻrinish bilan farq,
 'preferences' => 'Moslamalar',
 'mypreferences' => 'Moslamalarim',
 'prefs-edits' => 'Tahrirlar soni',
+'changepassword' => 'Maxfiy soʻzni oʻzgartirish',
 'prefs-skin' => 'Tashqi ko‘rinishi',
 'datedefault' => 'Farqi yoʻq',
 'prefs-datetime' => 'Sana va vaqt',
@@ -459,19 +463,22 @@ Bu yerda: (joriy) = hozirgi koʻrinish bilan farq,
 'prefs-watchlist' => "Kuzatuv ro'yxati",
 'prefs-watchlist-days-max' => 'Eng ko‘pi $1 {{PLURAL:$1|kun|kun}}',
 'prefs-misc' => 'Boshqa moslamalar',
+'prefs-resetpass' => 'Maxfiy soʻzni oʻzgartirish',
+'prefs-changeemail' => 'E-mail manzilingizni o‘zgartirish',
 'prefs-rendering' => 'Tashqi ko‘rinishi',
 'saveprefs' => 'Saqlash',
 'resetprefs' => 'Bekor qilish',
-'restoreprefs' => 'Barcha moslamalarni dastlabki holiga qaytarish',
+'restoreprefs' => 'Barcha moslamalarni dastlabki holiga qaytar',
 'prefs-editing' => 'Tahrirlash',
 'prefs-edit-boxsize' => 'Tahrir oynasining oʻlchami',
 'rows' => 'Qatorlar soni:',
 'columns' => 'Ustunlar soni:',
 'searchresultshead' => 'Qidiruv natijalari',
 'recentchangesdays-max' => 'Eng koʻpi $1 kun',
-'recentchangescount' => 'Sukut boʻyicha koʻrsatildigan tahrirlar soni',
-'timezonelegend' => 'Vaqt mintaqasi:',
+'recentchangescount' => 'Sukut boʻyicha koʻrsatiladigan tahrirlar soni',
+'timezonelegend' => 'Vaqt mintaqangiz:',
 'localtime' => 'Mahalliy vaqt:',
+'servertime' => 'Server vaqti:',
 'timezoneregion-africa' => 'Afrika',
 'timezoneregion-america' => 'Amerika',
 'timezoneregion-antarctica' => 'Antarktika',
@@ -491,9 +498,11 @@ Bu yerda: (joriy) = hozirgi koʻrinish bilan farq,
 'prefs-textboxsize' => 'Tahrir oynasining oʻlchami',
 'youremail' => 'E-mail:',
 'username' => 'Foydalanuvchi nomi',
-'prefs-memberingroups' => '{{PLURAL:$1|Guruh|Guruhlar}} aʼzosi:',
+'uid' => 'Identifikator:',
+'prefs-memberingroups' => 'Qaysi {{PLURAL:$1|guruh|guruhlar}} aʼzosi:',
 'prefs-registration' => 'Hisob ochilgan vaqt',
 'yourrealname' => 'Haqiqiy ism *:',
+'yourlanguage' => 'Til:',
 'yournick' => 'Yangi imzo',
 'prefs-help-signature' => 'Munozara sahifalarida imzo "<nowiki>~~~~</nowiki>" orqali qoʻyiladi (u sizning imzoingiz va joriy vaqtga aylantiriladi).',
 'yourgender' => 'Jinsi:',
@@ -505,6 +514,7 @@ Bu yerda: (joriy) = hozirgi koʻrinish bilan farq,
 'prefs-info' => 'Asosiy maʼlumot',
 'prefs-signature' => 'Imzo',
 'prefs-dateformat' => 'Sana formati',
+'prefs-timeoffset' => 'Vaqt farqi',
 'prefs-advancedediting' => 'Qoʻshimcha moslamalar',
 'prefs-advancedrc' => 'Qoʻshimcha moslamalar',
 'prefs-advancedrendering' => 'Qoʻshimcha moslamalar',
@@ -866,8 +876,8 @@ Yaqinda sodir etilgan yoʻqotishlar uchun $2ni koʻring.',
 'nextdiff' => 'Keyingi tahrir →',
 
 # Media information
-'imagemaxsize' => "Tasvir ta'rifi sahifasidagi tasvirning kattaligi:",
-'thumbsize' => 'Tasvirning kichiklashtirilgan versiyasining kattaligi:',
+'imagemaxsize' => 'Tasvir taʼrifi sahifasidagi tasvirning oʻlchami:',
+'thumbsize' => 'Tasvirning kichiklashtirilgan versiyasining oʻlchami:',
 'file-info-size' => '$1 × $2 piksel, fayl hajmi: $3, MIME tipi: $4',
 'file-nohires' => 'Bundan kattaroq tasvir yoʻq.',
 'svg-long-desc' => 'SVG fayl, asl oʻlchamlari $1 × $2 piksel, fayl hajmi: $3',
index fe0a471..ad58050 100644 (file)
@@ -706,6 +706,7 @@ Tài khoản của bạn đã mở.
 'remembermypassword' => 'Nhớ thông tin đăng nhập của tôi trên máy tính này (cho đến $1 ngày)',
 'securelogin-stick-https' => 'Giữ kết nối với HTTPS sau khi đăng nhập',
 'yourdomainname' => 'Tên miền của bạn:',
+'password-change-forbidden' => 'Bạn không thể đổi mật khẩu trên wiki này.',
 'externaldberror' => 'Có lỗi khi xác nhận cơ sở dữ liệu bên ngoài hoặc bạn không được phép cập nhật tài khoản bên ngoài.',
 'login' => 'Đăng nhập',
 'nav-login-createaccount' => 'Đăng nhập / Mở tài khoản',
@@ -947,6 +948,10 @@ hoặc [{{fullurl:{{FULLPAGENAME}}|action=edit}} sửa đổi trang này]</span>
 'noarticletext-nopermission' => 'Trang này hiện đang trống.
 Bạn có thể [[Special:Search/{{PAGENAME}}|tìm kiếm tựa trang này]] tại các trang khác,
 hoặc <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} tìm kiếm các nhật trình liên quan]</span>.',
+'missing-revision' => 'Phiên bản #$1 của trang có tên “{{PAGENAME}}” không tồn tại.
+
+Lỗi này thường xuất hiện đối khi theo dõi liên kết lỗi thời đến phiên bản cũ của một trang đã bị xóa.
+Xem chi tiết trong [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} nhật trình xóa].',
 'userpage-userdoesnotexist' => 'Tài khoản mang tên “<nowiki>$1</nowiki>” chưa được đăng ký. Xin hãy kiểm tra lại nếu bạn muốn tạo/sửa trang này.',
 'userpage-userdoesnotexist-view' => 'Tài khoản “$1” chưa được đăng ký.',
 'blocked-notice-logextract' => 'Người dùng này hiện đang bị cấm sửa đổi. Nhật trình cấm gần nhất được ghi ở dưới để tiện theo dõi:',
@@ -1071,6 +1076,7 @@ Những giá trị này sẽ bị bỏ đi.',
 'expansion-depth-exceeded-warning' => 'Trang bung bản mẫu sâu quá',
 'parser-unstrip-loop-warning' => 'Vòng lặp unstrip',
 'parser-unstrip-recursion-limit' => 'Đã vượt quá giới hạn về độ sâu đệ quy unstrip ($1)',
+'converter-manual-rule-error' => 'Lỗi được phát hiện trong quy tắc chuyển đổi ngôn ngữ thủ công',
 
 # "Undo" feature
 'undo-success' => 'Các sửa đổi có thể được lùi lại. Xin hãy kiểm tra phần so sánh bên dưới để xác nhận lại những gì bạn muốn làm, sau đó lưu thay đổi ở dưới để hoàn tất việc lùi lại sửa đổi.',
@@ -1257,6 +1263,10 @@ Xin hãy bảo đảm giữ vững tính liên tục của lịch sử trang.',
 'editundo' => 'lùi sửa',
 'diff-multi' => '(Không hiển thị {{PLURAL:$1||$1}} phiên bản {{PLURAL:$2||của $2 thành viên}} ở giữa)',
 'diff-multi-manyusers' => '(Không hiển thị {{PLURAL:$1||$1}} phiên bản của hơn $2 thành viên ở giữa)',
+'difference-missing-revision' => 'Không tìm thấy {{PLURAL:$2|một phiên bản|$2 phiên bản}} trong khác biệt này ($1).
+
+Lỗi này thường xuất hiện đối khi theo dõi liên kết lỗi thời đến khác biệt giữa các bản của trang đã bị xóa.
+Xem chi tiết trong [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} nhật trình xóa].',
 
 # Search results
 'searchresults' => 'Kết quả tìm kiếm',
@@ -1524,12 +1534,13 @@ Nếu bạn đồng ý cung cấp, nó sẽ dùng để ghi nhận công lao c
 'right-writeapi' => 'Sử dụng API để viết',
 'right-delete' => 'Xóa trang',
 'right-bigdelete' => 'Xóa trang có lịch sử lớn',
+'right-deletelogentry' => 'Xóa và phục hồi khoản mục nhật trình nào đó',
 'right-deleterevision' => 'Xóa và phục hồi phiên bản nào đó của trang',
 'right-deletedhistory' => 'Xem phần lịch sử đã xóa, mà không xem nội dung đi kèm',
 'right-deletedtext' => 'Xem văn bản đã xóa và các thay đổi giữa phiên bản đã xóa',
 'right-browsearchive' => 'Tìm kiếm trang đã bị xóa',
 'right-undelete' => 'Phục hồi trang',
-'right-suppressrevision' => 'Xem lại và phục hồi phiên bản mà Sysop không thấy',
+'right-suppressrevision' => 'Xem và phục hồi phiên bản mà bảo quản viên không thấy',
 'right-suppressionlog' => 'Xem nhật trình riêng tư',
 'right-block' => 'Cấm thành viên khác sửa đổi',
 'right-blockemail' => 'Cấm người dùng gửi thư điện tử',
@@ -1588,7 +1599,7 @@ Nếu bạn đồng ý cung cấp, nó sẽ dùng để ghi nhận công lao c
 'action-deletedhistory' => 'xem các phiên bản đã bị xóa của trang này',
 'action-browsearchive' => 'tìm kiếm trang đã bị xóa',
 'action-undelete' => 'phục hồi trang này',
-'action-suppressrevision' => 'duyệt và phục hồi phiên bản bị giấu này',
+'action-suppressrevision' => 'xem và phục hồi phiên bản ẩn này',
 'action-suppressionlog' => 'xem nhật trình ẩn giấu này',
 'action-block' => 'cấm không cho người dùng này sửa đổi',
 'action-protect' => 'thay đổi mức khóa của trang này',
@@ -2036,7 +2047,7 @@ Hãy nhớ kiểm tra các liên kết khác đến bản mẫu trước khi xó
 
 'disambiguations' => 'Trang liên kết đến trang định hướng',
 'disambiguationspage' => 'Template:disambig',
-'disambiguations-text' => "Các trang này có liên kết đến một '''trang định hướng'''. Nên sửa các liên kết này để chỉ đến một trang đúng nghĩa hơn.<br />Các trang định hướng là trang sử dụng những bản mẫu được liệt kê ở [[MediaWiki:Disambiguationspage]].",
+'disambiguations-text' => "Các trang này có liên kết đến ít nhất một '''trang định hướng''', những trang này có thể có liên kết đến các trang đúng nghĩa hơn.<br />Các trang định hướng là trang sử dụng những bản mẫu được liệt kê ở [[MediaWiki:Disambiguationspage]].",
 
 'doubleredirects' => 'Đổi hướng kép',
 'doubleredirectstext' => 'Trang này liệt kê các trang đổi hướng đến một trang đổi hướng khác.
@@ -2377,6 +2388,8 @@ hãy cẩn trọng khi thực hiện.',
 'rollback' => 'Lùi tất cả sửa đổi',
 'rollback_short' => 'Lùi tất cả',
 'rollbacklink' => 'lùi tất cả',
+'rollbacklinkcount' => 'lùi tất cả $1 sửa đổi',
+'rollbacklinkcount-morethan' => 'lùi tất cả hơn $1 sửa đổi',
 'rollbackfailed' => 'Lùi sửa đổi không thành công',
 'cantrollback' => 'Không lùi sửa đổi được;
 người viết trang cuối cùng cũng là tác giả duy nhất của trang này.',
@@ -2984,7 +2997,7 @@ Lưu nó vào máy tính của bạn rồi tải nó lên đây.',
 'tooltip-watchlistedit-raw-submit' => 'Cập nhật danh sách theo dõi',
 'tooltip-recreate' => 'Tạo lại trang dù cho nó vừa bị xóa',
 'tooltip-upload' => 'Bắt đầu tải lên',
-'tooltip-rollback' => '"Lùi tất cả" sẽ lùi mọi sửa đổi của người sửa đổi cuối cùng chỉ bằng một cú nhấp chuột.',
+'tooltip-rollback' => '“Lùi tất cả” sẽ lùi mọi sửa đổi của người sửa đổi cuối cùng chỉ bằng một cú nhấp chuột.',
 'tooltip-undo' => '"Lùi lại" sẽ lùi sửa đổi này và mở trang sửa đổi ở chế độ xem thử. Cho phép thêm lý do vào tóm lược.',
 'tooltip-preferences-save' => 'Lưu tùy chọn',
 'tooltip-summary' => 'Hãy nhập câu tóm lược',
index cf2c1fa..98bd632 100644 (file)
@@ -281,6 +281,8 @@ Võitta loovva cüľľee cirjuttamall alla õlõvasõõ tilaa. Kui että taho lo
 'noarticletext' => '{{GRAMMAR:inessive|{{SITENAME}}}} ebõõ sene nimissä cülciä.
 * Võid [[Special:Search/{{PAGENAME}}|ettsiä cüľľee nimellä]] muilta cüľľeltä.
 * Võid cirjuttõma uuvvõõ cüľľee <span class="plainlinks">[{{fullurl:{{FULLPAGENAME}}|action=edit}} {{PAGENAME}}]</span>.',
+'noarticletext-nopermission' => 'Paraika kazell lehocüllell eb õõ teksta.
+Tüü võittõ [[Special:Search/{{PAGENAME}}|kaze nime nimettamizõ löütä]] muiss artikkeliiss, ehci <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}löütä azjakõhaizõd cirjauhsõd žurnaaliiss]</span>.',
 'previewnote' => "'''Kase on ainult prestavleńńa.''' Cülciä ebõõ veel salvotõttu!",
 'editing' => 'Muutun $1',
 'editingsection' => 'Muuttõmizõll on õsa cüľľess $1',
@@ -293,6 +295,12 @@ Võitta loovva cüľľee cirjuttamall alla õlõvasõõ tilaa. Kui että taho lo
 'permissionserrorstext-withaction' => 'Teill ebõõ luppa $2 {{PLURAL:$1|vahtiaavass süüss|vahtiaaviss süiss}} peräss:',
 'moveddeleted-notice' => "Kase cülci on pühittü. Alla on sene cüľľee pühi'istori.",
 
+# Parser/template warnings
+'post-expand-template-inclusion-warning' => "'''Etetäätämin:''' lizettävije šabloonije summaarin koko on liiga suuri.
+Mõnõd šabloonad eväd lee lizettü.",
+'post-expand-template-inclusion-category' => 'Lehocülled, jõgõit vart lizettävije šabloonije sallittu koko on ületettü',
+'post-expand-template-argument-category' => 'Lehocülled, jõgad sisälletä väl’l’ä-jätettü šabloonije argumentad.',
+
 # History pages
 'viewpagelogs' => 'Näüt sene cüľľee logid',
 'currentrev' => 'Nücüin verzija',
@@ -305,6 +313,7 @@ Võitta loovva cüľľee cirjuttamall alla õlõvasõõ tilaa. Kui että taho lo
 'last' => 'entin',
 'histlegend' => "Merkid: ({{int:cur}}) = vahõ nütšüizese verzijaa, ({{int:last}}) = vahõ entiizese verzijaa, '''{{int:minoreditletter}}''' = peeni muutuz",
 'history-fieldset-title' => 'Ľistvoit muutuzistoria',
+'history-show-deleted' => "Tol'ko poisõttu",
 'histfirst' => 'Kõikkõa varaizõpid',
 'histlast' => 'Kõikkõa viimõizõpid',
 
@@ -319,6 +328,8 @@ Võitta loovva cüľľee cirjuttamall alla õlõvasõõ tilaa. Kui että taho lo
 'revdelete-radio-unset' => 'Eb',
 'revdelete-log' => 'Süü:',
 'revdel-restore' => 'muutu näcüvüss',
+'revdel-restore-deleted' => 'poizõttu verssijad',
+'revdel-restore-visible' => 'nähtäväd verssijad',
 'pagehist' => 'Cüľľee istori',
 'revdelete-reasonotherlist' => 'Muu süü',
 
@@ -344,12 +355,22 @@ Võitta loovva cüľľee cirjuttamall alla õlõvasõõ tilaa. Kui että taho lo
 'notextmatches' => 'Ettsisõnaa eb löütünnü cüľľee tekstiissä',
 'prevn' => '{{PLURAL:$1|edellinen|$1 entiiss}}',
 'nextn' => '{{PLURAL:$1|$1}} vahtiaava →',
+'prevn-title' => '{{PLURAL:$1|Eellin cirjauz|$1 eellissa cirjaussa}}',
+'nextn-title' => '{{PLURAL:$1|Sõurava cirjauz|$1 sõurava cirjaussa}}',
+'shown-title' => 'Näüttä $1 {{PLURAL:$1|cirjauz|cirjaussa}} lehocüllell',
 'viewprevnext' => 'Näüt ($1 {{int:pipe-separator}} $2) ($3)',
+'searchmenu-exists' => 'Kazez viki-projeektaz on lehocülci «[[:$1]]» õõmaz',
+'searchmenu-new' => 'Tehä lehocülci «[[:$1]]» senez viki-projeektaz!',
 'searchprofile-articles' => 'Pääcülcid',
+'searchprofile-project' => 'Selvitühse ja projeekta lehocülled',
 'searchprofile-images' => 'Multimedia',
 'searchprofile-everything' => 'Kõik',
+'searchprofile-advanced' => 'Lizätez',
 'searchprofile-articles-tooltip' => 'Etsi "$1":az',
 'searchprofile-project-tooltip' => 'Etsi "$1":az',
+'searchprofile-images-tooltip' => 'Failõje õttsigo',
+'searchprofile-everything-tooltip' => 'Õttsigo kõikill lehocüllill (ceskussõõmizõ lehocülled siällhulgaz)',
+'searchprofile-advanced-tooltip' => 'Annõttu nimije tiloiz õttsia',
 'search-result-size' => '$1 ({{PLURAL:$2|1 sõna|$2 sõna}})',
 'search-redirect' => '(mešaituz $1)',
 'search-section' => '(alajako $1)',
@@ -361,8 +382,10 @@ Võitta loovva cüľľee cirjuttamall alla õlõvasõõ tilaa. Kui että taho lo
 'search-mwsuggest-disabled' => 'tarittsõmizõtta',
 'searchrelated' => 'sittu',
 'searchall' => 'kõik',
+'showingresultsheader' => "{{PLURAL:$5|Tuloz '''$1''' '''$3'''-ss|Tulohsõd '''$1-$2''' '''$3'''-ss}} «$4» vart",
 'nonefound' => "'''Zametšańńa''': Ettsü etsib anult osiss nimiruumii.
 Proovvi lizät etsün alkuu ''all:'', nii ettsü etsib kõikkõõ sisältoo (taas juttucüľľeekaa, sablooniikaa, jne.), vai tarvittõga nimiruumi niku prefiksi.",
+'search-nonefound' => 'Cüsümühse mukaizõssi eb õõ mitäid löütettü.',
 'powersearch' => 'Etenennü ettsü',
 'powersearch-legend' => 'Etenennü ettsü',
 'powersearch-ns' => 'Etsi nimiruumõssa:',
@@ -420,7 +443,10 @@ Proovvi lizät etsün alkuu ''all:'', nii ettsü etsib kõikkõõ sisältoo (taa
 'recentchanges' => 'Viimõizõd muutussõd',
 'recentchanges-legend' => 'Viimass muutuhsõss valimizõd',
 'recentchanges-feed-description' => 'Sell sivull võib vahtia uutizijõ muutuhsiit.',
+'recentchanges-label-newpage' => 'Kaze kirjauhsõka õli muu lehocülci lootu.',
 'recentchanges-label-minor' => 'Kase on peeni muutuz',
+'recentchanges-label-bot' => 'Kase kõrjauz on robotaka lootu',
+'recentchanges-label-unpatrolled' => 'Kasta kõrjaussa eb tarkisõtti veel',
 'rcnote' => 'Alla on {{PLURAL:$1|ühsi muutuz|viimeiziit $1 muutussiit}} viimeize {{PLURAL:$2|ühee päivää|$2 päivää}}, $4 $5.',
 'rclistfrom' => 'Näüt uuvvõd muutuhsõd $1 alguss',
 'rcshowhideminor' => '$1 peened muutussõd',
@@ -444,6 +470,7 @@ Proovvi lizät etsün alkuu ''all:'', nii ettsü etsib kõikkõõ sisältoo (taa
 'recentchangeslinked-feed' => 'Sukulaizõd muutussõd',
 'recentchangeslinked-toolbox' => 'Sukulaizõd muutussõd',
 'recentchangeslinked-title' => 'Cüľľelt $1 linkitettüďďe cülcije muutuhsõd.',
+'recentchangeslinked-noresult' => 'Cüseizez aigakõhaz eväd õltu milliziitäid muuttumiziit.',
 'recentchangeslinked-summary' => "Kase osoobennoi cülci näütteb muutusõd cülcillä, kummalõõ on seltä cüľľeltä näüteltü.
 Cüľľed, kummad õmad teďďellä [[Special:Watchlist|kattsõspiizgalla]] on cirjuttõnnu '''pimmiässi'''.",
 'recentchangeslinked-page' => 'Cüľľee nimi:',
@@ -483,7 +510,10 @@ Cüľľed, kummad õmad teďďellä [[Special:Watchlist|kattsõspiizgalla]] on c
 'filehist-comment' => 'Zametšańńa',
 'imagelinks' => 'Faililinkid',
 'linkstoimage' => 'Selle kuvallõ {{PLURAL:$1|näütteeb kase сülсi|näütteväd kaned сüľľed}}:',
+'nolinkstoimage' => 'Eb ühelläid lehocüllell cäütetä kase faila.',
 'sharedupload' => 'Kase faili on $1:lt ja muud projektõd saavad cäüttää sitä.',
+'sharedupload-desc-here' => 'Se fajla on $1-ss ja võib muiz projeektiz õlla cäütettävänä.
+Seness [$2 kuvauhsõ lehocülless] informaattsija on alapallõ annõttu.',
 'uploadnewversion-linktext' => 'Laajõngoit uusi verzija seness failiss ülez',
 
 # File reversion
@@ -546,6 +576,7 @@ Cüľľed, kummad õmad teďďellä [[Special:Watchlist|kattsõspiizgalla]] on c
 'linksearch' => 'Ulkopoolizõd linkid',
 'linksearch-ns' => 'Nimiruumi:',
 'linksearch-ok' => 'Etsi',
+'linksearch-line' => 'Linki $1-sõ $2-ss',
 
 # Special:ListUsers
 'listusers-submit' => 'Näüt väľľää',
@@ -899,6 +930,7 @@ Kui faili on muutõttu, siiz detaaľid võivad õlla kahõllaizõd muutõtull fa
 'specialpages' => 'Osoobenoid cüľľed',
 
 # Special:Tags
+'tag-filter' => "[[Special:Tags|Deskriptorije]] fil'tra:",
 'tags-edit' => 'muuttaa',
 
 # HTML forms
index ba70453..8614132 100644 (file)
@@ -353,7 +353,7 @@ $1',
 'nstab-project' => 'Pakli han proyekto',
 'nstab-image' => 'Fayl',
 'nstab-mediawiki' => 'Mensahe',
-'nstab-template' => 'Plantilya',
+'nstab-template' => 'Batakan',
 'nstab-help' => 'Pakli hin bulig',
 'nstab-category' => 'Kaarangay',
 
@@ -948,13 +948,13 @@ An paglaladawan han iya [$2 fayl han paglaladawan nga pakli] didto in ginpapakit
 'statistics-articles' => 'Unod nga mga pakli',
 'statistics-pages' => 'Mga pakli',
 'statistics-pages-desc' => 'Ngatanan nga mga pakli ha sulod hini nga wiki, lakip an hiruhimangraw nga mga pakli, mga redirect, ngan iba pa',
-'statistics-files' => 'Ginkarga nga mga paypay',
+'statistics-files' => 'Mga paypay nga iginkarga pasaka',
 'statistics-edits' => 'Mga pagliwat hit pakli tikang gintukod hini nga {{SITENAME}}',
 'statistics-edits-average' => 'Average nga mga pagliwat kada pakli',
 'statistics-views-total' => 'Ngatanan nga mga panginano',
 'statistics-views-peredit' => 'Mga panginano kada pagliwat',
-'statistics-users' => 'Mga nakarehistro nga [[Special:ListUsers|gumaramit]]',
-'statistics-users-active' => 'Mga nanggigios nga gumaramit',
+'statistics-users' => 'Mga [[Special:ListUsers|gumaramit]] nga nakarehistro',
+'statistics-users-active' => 'Mga gumaramit nga nanggigios',
 'statistics-users-active-desc' => 'Mga gumaramit nga may-ada iginbuhat ha urhi nga {{PLURAL:$1|ka adlaw|$1 ka mga adlaw}}',
 'statistics-mostpopular' => 'Gidamoi nga ginpanginanohan nga mga pakli',
 
@@ -1050,7 +1050,7 @@ An paglaladawan han iya [$2 fayl han paglaladawan nga pakli] didto in ginpapakit
 'listusers-blocked' => '(ginpugngan)',
 
 # Special:ActiveUsers
-'activeusers' => 'Lista han mga nanggigios nga gumaramit',
+'activeusers' => 'Taramdan hin mga gumaramit nga nanggigios',
 'activeusers-hidebots' => 'Igtago an mga bot',
 'activeusers-hidesysops' => 'Igtago an mga magdudumara',
 'activeusers-noresult' => 'Waray gumaramit nga nahiagian.',
index 18a429e..1b52c4c 100644 (file)
@@ -450,6 +450,10 @@ $1',
 'youhavenewmessages' => 'איר האט $1 ($2).',
 'newmessageslink' => 'נייע מעלדונגען',
 'newmessagesdifflink' => 'לעצטע ענדערונג',
+'youhavenewmessagesfromusers' => 'איר האט $1 פון {{PLURAL:$3|אן אנדער באניצער|$3 באניצער}} ($2).',
+'youhavenewmessagesmanyusers' => 'איר האט $1 פון אסאך באניצער ($2).',
+'newmessageslinkplural' => '{{PLURAL:$1|א נייע מעלדונג|נייע מעלדונגען}}',
+'newmessagesdifflinkplural' => 'לעצטע {{PLURAL:$1|ענדערונג|ענדערונגען}}',
 'youhavenewmessagesmulti' => 'איר האט נייע מעלדונגען אין $1',
 'editsection' => 'באַאַרבעטן',
 'editold' => 'רעדאַקטירן',
@@ -578,6 +582,8 @@ $2',
 דער סיסאפ וואס האט זי פארשפארט האט געגעבן דעם הסבר:  "$3"',
 'invalidtitle-knownnamespace' => 'אומגילטירער טיטל מיט נאמענטייל "$2" און טעקסט "$3"',
 'invalidtitle-unknownnamespace' => 'אומגילטיקער טיטל מיט אומבאוואוסטן נאמענטייל נומער $1 און טעקסט "$2"',
+'exception-nologin' => 'נישט אַרײַנלאגירט',
+'exception-nologin-text' => 'דער בלאט אדער אקציע פֿאדערט אז איר זענט אריינלאגירט ביי דער וויקי.',
 
 # Virus scanner
 'virus-badscanner' => "שלעכטע קאנפֿיגוראציע: אומבאוואוסטער ווירוס איבערקוקער: ''$1''",
@@ -596,6 +602,7 @@ $2',
 'remembermypassword' => 'געדיינק מײַן אַרײַנלאגירן אויף דעם קאמפיוטער (ביז  $1 {{PLURAL:$1|טאָג|טעג}})',
 'securelogin-stick-https' => 'בלייַבן פארבונדן צו HTTPS נאָכן ארײַנלאָגירן',
 'yourdomainname' => 'אײַער געביט:',
+'password-change-forbidden' => 'איר קען נישט ענדערן פאסווערטער אויף דער וויקי.',
 'externaldberror' => 'עס איז אדער פארגעקומען אן אויטענטיקאציע דאטנבאזע פעלער אדער איר זענט נישט ערמעגליכט צו דערהיינטיגן אייער דרויסנדיגע קאנטע.',
 'login' => 'אַרײַנלאָגירן',
 'nav-login-createaccount' => 'ארײַנלאָגירן / זיך אײַנשרײַבן',
@@ -1600,8 +1607,9 @@ $1",
 'tmp-write-error' => 'טעות בײַם שרייַבן צייַטווייַליקע טעקע.',
 'large-file' => 'רעקאמענדירט אז טעקעס זאל נישט זײַן גרעסער פֿון$1;
 די טעקע איז $2.',
+'largefileserver' => 'די טעקע איז גרעסער פונעם מאקסימום פאר דעם סערווער.',
 'emptyfile' => 'די טעקע וואס איר האט ארויפֿלגעלאָדן איז ליידיג.
-עס קען זיין אז די סיבה איז פשוט א טייפא.
+עס קען זיין אז די סיבה איז פשוט א טייפא אינעם טעקע־נאמען.
 ביטע קוקט איבער צי איר ווילט ארויפֿלאדן  די דאזיקע טעקע.',
 'windows-nonascii-filename' => 'די וויקי שטיצט נישט טעקע־נעמען מיט ספעציעלע צייכענען.',
 'fileexists' => "א טעקע מיט דעם נאָמען עקזיסטירט שוין, ביטע זײַט בודק '''<tt>[[:$1]]</tt>''' ווען איר זענט נישט זיכער אַז איר ווילט זי ענדערן.
@@ -1835,7 +1843,7 @@ $1",
 
 'disambiguations' => 'בלעטער וואס פֿארבינדן מיט באדייטן בלעטער',
 'disambiguationspage' => 'Template:באדייטן',
-'disambiguations-text' => "×\93×\99 ×§×\95×\9e×¢× ×\93×\99×\92×¢ ×\91×\9c×¢×\98ער ×¤×\90ר×\91×\99× ×\93×¢×\9f ×¦×\95 ×\90 '''×\91×\90×\93×\99×\99×\98×\9f ×\91×\9c×\90×\98'''. ×\96×\99×\99 ×\91ר×\95×\99×\9b×\9f ×¢× ×\93ערש×\98 ×¤Ö¿×\90ר×\91×\99× ×\93×\9f ×¦×\95 ×\93ער ×¨×¢×\9c×¢×\95×\95×\90× ×\98ער ×\98×¢×\9e×¢ ×\91×\9c×\90×\98.<br />×\90 ×\91×\9c×\90×\98 ×\95×\95ער×\98 ×¤×\90ררע×\9b×¢× ×\98 ×\90×\9cס ×\90 ×\91×\9c×\90×\98 ×\95×\95ער×\98 ×\92ערע×\9bנט פאר א באדײַטן בלאט אויב ער באניצט זיך מיט א מוסטער וואס איז פארבינדען פון [[MediaWiki:Disambiguationspage]].",
+'disambiguations-text' => "×\93×\99 ×§×\95×\9e×¢× ×\93×\99×\92×¢ ×\91×\9c×¢×\98ער ×¤×\90ר×\91×\99× ×\93×\9f ×¦×\95 ×\90 '''×\91×\90×\93×\99×\99×\98×\9f ×\91×\9c×\90×\98'''. ×\96×\99×\99 ×\91ר×\95×\99×\9b×\9f ×¢× ×\93ערש×\98 ×¤Ö¿×\90ר×\91×\99× ×\93×\9f ×¦×\95 ×\93×¢×\9d ×¨×¢×\9c×¢×\95×\95×\90× ×\98×\9f ×\98×¢×\9e×¢ ×\91×\9c×\90×\98.<br />×\90 ×\91×\9c×\90×\98 ×\95×\95ער×\98 ×¤×\90ררע×\9b×¢נט פאר א באדײַטן בלאט אויב ער באניצט זיך מיט א מוסטער וואס איז פארבינדען פון [[MediaWiki:Disambiguationspage]].",
 
 'doubleredirects' => 'געטאפלטע ווײַטערפֿירונגען',
 'doubleredirectstext' => 'דער בלאט רעכנט אויס בלעטער וואס פירן ווייטער צו אנדערע ווייטערפירן בלעטער.
@@ -1880,6 +1888,7 @@ $1",
 'wantedpages' => 'געזוכטע בלעטער',
 'wantedpages-badtitle' => 'אומגילטיקער טיטל אין רעזולטאַט: $1',
 'wantedfiles' => 'געזוכטע טעקעס',
+'wantedfiletext-cat' => 'די פֿאלגנדע טעקעס ווערן געניצט אבער זיי עקזיסטירן נישט. טעקעס פון פֿרעמדע רעפאזיטאריעס קענען ווערן אריינגערעכנט טראץ זיי עקזיסטירן יא. אזעלכע גרייזן וועלן ווערן <del>אויסגעשריכן </del>. דערצו, בלעטער וואס ניצן אומעקזיסטירנדע טעקעס ווערן אריינגערעכנט אין [[:$1]].',
 'wantedtemplates' => 'געזוכטע מוסטערן',
 'mostlinked' => 'מערסט פֿארבינדענע בלעטער',
 'mostlinkedcategories' => 'מערסט פֿארבינדענע קאטעגאריעס',
@@ -1939,6 +1948,7 @@ $1",
 מען קען פֿאַרשמעלרן די אויסוואל דורך אויסוויילן א סארט לאג, באַניצער נאמען אדער אנרירנדע בלעטער.',
 'logempty' => 'נישטא קיין פאַסנדיקע זאכן אין לאג.',
 'log-title-wildcard' => 'זוכן טיטלען וואס הייבן אָן מיט דעם טעקסט',
+'showhideselectedlogentries' => 'ווײַזן/באַהאַלטן געקליבענע לאגבוך אקציעס',
 
 # Special:AllPages
 'allpages' => 'אַלע בלעטער',
@@ -2169,6 +2179,8 @@ $UNWATCHURL
 'rollback' => 'צוריקדרייען רעדאַקטירונגען',
 'rollback_short' => 'צוריקדרייען',
 'rollbacklink' => 'צוריקדרייען',
+'rollbacklinkcount' => 'צוריקדרייען $1 {{PLURAL:$1|רעדאקטירונג|רעדאקטירונגען}}',
+'rollbacklinkcount-morethan' => 'צוריקדרייען מער ווי $1 {{PLURAL:$1|רעדאקטירונג|רעדאקטירונגען}}',
 'rollbackfailed' => 'צוריקדרייען דורכגעפֿאַלן',
 'cantrollback' => 'מען קען נישט צוריקדרייען די ענדערונג – דער לעצטער בײַשטייערער איז דער איינציגסטער שרײַבער אין דעם בלאַט.',
 'alreadyrolled' => 'מען קען נישט צוריקדרייען די לעצטע ענדערונג פון בלאט [[:$1]] פֿון
@@ -2553,6 +2565,7 @@ $1',
 'movepage-page-exists' => "דער בלאַט $1 עקזיסטירט שוין און מ'קען אים נישט אויטאָמאַטיש איבערשרײַבן.",
 'movepage-page-moved' => 'דער בלאַט $1 איז געוורן באַוועגט צו $2.',
 'movepage-page-unmoved' => 'מען קען נישט באוועגן בלאט $1 צו $2.',
+'movepage-max-pages' => 'דער מאקסימום פון $1 {{PLURAL:$1|בלאט|בלעטער}} האט מען שוין באוועגט און נאך בלעטער וועט מען נישט באוועגן אויטאמאטיש.',
 'movelogpage' => 'באוועגן לאג',
 'movelogpagetext' => 'פֿאלגנד איז א ליסטע פֿון  בלעטער באוועגט.',
 'movesubpage' => '{{PLURAL:$1|אונטערבלאַט|אונטערבלעטער}}',
@@ -2993,7 +3006,9 @@ $1',
 'exif-scenecapturetype' => 'סצענע אויפנעם טיפ',
 'exif-gaincontrol' => 'סצענע קאנטראל',
 'exif-contrast' => 'קאנטראסט',
-'exif-devicesettingdescription' => 'זאך סעטינגס אראפמאלונג',
+'exif-saturation' => 'זעטיקונג',
+'exif-sharpness' => 'שארף',
+'exif-devicesettingdescription' => 'אפאראט שטעלונגען אראפמאלונג',
 'exif-gpslatituderef' => 'צפון אדער דרום גארטל־ליניע',
 'exif-gpslatitude' => 'גארטל־ליניע',
 'exif-gpslongituderef' => 'מזרח אדער מערב לענג',
@@ -3031,6 +3046,9 @@ $1',
 'exif-provinceorstatedest' => 'פראווינץ אדער שטאַט געוויזן',
 'exif-citydest' => 'געוויזענע שטָאט',
 'exif-objectname' => 'קורצער טיטל',
+'exif-specialinstructions' => 'באזונדערע אנווייזונגען',
+'exif-headline' => 'קעפל',
+'exif-credit' => 'קרעדיט/פארזארגער',
 'exif-source' => 'מקור',
 'exif-editstatus' => 'רעדאקציאנעלער סטאטוס פון בילד',
 'exif-urgency' => 'דרינגלעכקייט',
@@ -3110,6 +3128,8 @@ $1',
 
 'exif-focalplaneresolutionunit-2' => 'אינטשעס',
 
+'exif-sensingmethod-1' => 'אומדעפינירט',
+
 'exif-customrendered-0' => 'נארמאלער פראצעס',
 'exif-customrendered-1' => 'קאסטעם פראצעס',
 
@@ -3139,17 +3159,20 @@ $1',
 'exif-sharpness-2' => 'הארט',
 
 'exif-subjectdistancerange-0' => 'אומבאַוויסט',
+'exif-subjectdistancerange-1' => 'מאקרא',
+'exif-subjectdistancerange-2' => 'נאנטע ווייזונג',
+'exif-subjectdistancerange-3' => 'ווײַטע ווײַזונג',
 
 # Pseudotags used for GPSLatitudeRef and GPSDestLatitudeRef
-'exif-gpslatitude-n' => 'צפ×\95×\9f ×\9c×¢×\98×\99×\98×\95×\93',
-'exif-gpslatitude-s' => '×\93ר×\95×\9d ×\9c×\90×\98×\99×\98×\95×\93',
+'exif-gpslatitude-n' => 'צפ×\95×\9f ×\91ר×\99×\99×\98',
+'exif-gpslatitude-s' => '×\93ר×\95×\9d ×\91ר×\99×\99×\98',
 
 # Pseudotags used for GPSLongitudeRef and GPSDestLongitudeRef
 'exif-gpslongitude-e' => 'מזרח לענג',
 'exif-gpslongitude-w' => 'מערב לענג',
 
 # Pseudotags used for GPSAltitudeRef
-'exif-gpsaltitude-above-sealevel' => '$1 {{PLURAL:$1|ngמעטער|מעטער}} איבערן ים־שפיגלl',
+'exif-gpsaltitude-above-sealevel' => '$1 {{PLURAL:$1|מעטער|מעטער}} איבערן ים־שפיגלl',
 'exif-gpsaltitude-below-sealevel' => '$1 {{PLURAL:$1|מעטער|מעטער}} אונטערן ים־שפיגל',
 
 # Pseudotags used for GPSSpeedRef
@@ -3406,6 +3429,7 @@ $5
 
 # Special:Version
 'version' => 'ווערסיע',
+'version-extensions' => 'אינסטאלירטע פארברייטערונגען',
 'version-specialpages' => 'ספעציעלע בלעטער',
 'version-variables' => 'וואַריאַבלען',
 'version-skins' => 'באניצער־אייבערפלאכן',
index 99e59e8..06f489a 100644 (file)
@@ -536,6 +536,10 @@ $1',
 'youhavenewmessages' => '你有$1($2)。',
 'newmessageslink' => '新信息',
 'newmessagesdifflink' => '最后更改',
+'youhavenewmessagesfromusers' => '你有来自{{PLURAL:$3| 另一位用户| $3位用户}}的$1($2)。',
+'youhavenewmessagesmanyusers' => '你有来自多位用户的$1($2)。',
+'newmessageslinkplural' => '{{PLURAL:$1|一条新信息|$1条信息}}',
+'newmessagesdifflinkplural' => '最新$1次更改',
 'youhavenewmessagesmulti' => '你在$1有新信息',
 'editsection' => '编辑',
 'editold' => '编辑',
@@ -673,6 +677,7 @@ $2',
 'remembermypassword' => '在此浏览器上保留我的登录信息(最长$1{{PLURAL:$1|日|日}})',
 'securelogin-stick-https' => '登陆后继续使用 HTTPS 连接',
 'yourdomainname' => '您的域名:',
+'password-change-forbidden' => '您不能更改此wiki上的密码。',
 'externaldberror' => '这可能是由于验证数据库错误或您被禁止更新您的外部账号。',
 'login' => '登录',
 'nav-login-createaccount' => '登录/创建账户',
@@ -883,6 +888,10 @@ $2
 'noarticletext' => '本页面目前没有内容。你可以在其他页面中[[Special:Search/{{PAGENAME}}|搜索该页标题]]、<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜索相关日志]或[{{fullurl:{{FULLPAGENAME}}|action=edit}} 编辑本页面]。</span>',
 'noarticletext-nopermission' => '此页目前没有内容,您可以在其它页[[Special:Search/{{PAGENAME}}|搜索此页标题]],
 或<span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 搜索有关日志]</span>。',
+'missing-revision' => '“{{PAGENAME}}”的修订#$1不存在。
+
+这通常是因为进入了一个已被删除的页面的历史链接。
+详细信息可以在[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 删除日志]中找到。',
 'userpage-userdoesnotexist' => '用户账户"$1"未注册。
 请在创建/编辑该页之前进行核对。',
 'userpage-userdoesnotexist-view' => '用户账户“$1”未曾创建。',
@@ -1003,6 +1012,7 @@ $2
 'expansion-depth-exceeded-warning' => '页面超过了扩展深度',
 'parser-unstrip-loop-warning' => '检测到回圈',
 'parser-unstrip-recursion-limit' => '递归超过限制 ($1)',
+'converter-manual-rule-error' => '手动语言转换规则中检测到错误',
 
 # "Undo" feature
 'undo-success' => '此编辑可以被撤销。请检查以下比较以核实这正是您想做的,然后保存以下更改完成撤销编辑。',
@@ -1170,6 +1180,10 @@ $1",
 'editundo' => '撤销',
 'diff-multi' => '(未显示$2个用户的$1个中间版本)',
 'diff-multi-manyusers' => '(未显示超过$2个用户的$1个中间版本)',
+'difference-missing-revision' => '此差异对比的{{PLURAL:$2|一个修订|$2个修订}}($1){{PLURAL:$2|没有}}找到。
+
+这通常是因为进入了一个已被删除的页面的修订差异对比链接。
+详细信息可以在[{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 删除日志]中找到。',
 
 # Search results
 'searchresults' => '搜索结果',
@@ -1431,6 +1445,7 @@ $1",
 'right-writeapi' => '使用书写API',
 'right-delete' => '删除页面',
 'right-bigdelete' => '删除有大型历史的页面',
+'right-deletelogentry' => '删除和恢复特定的日志项目',
 'right-deleterevision' => '删除和恢复页面的特定版本',
 'right-deletedhistory' => '查看被删除的历史条目,无其相关文字',
 'right-deletedtext' => '查看被删除的版本间的被删除的文字和更改',
@@ -1916,7 +1931,7 @@ $1',
 
 'disambiguations' => '链接至消歧义页的页面',
 'disambiguationspage' => 'Template:消歧义',
-'disambiguations-text' => "以ä¸\8bç\9a\84页é\9d¢é\83½æ\9c\89å\88°'''æ¶\88æ­§ä¹\89页'''ç\9a\84é\93¾æ\8e¥ï¼\8cä½\86å®\83们åº\94该é\93¾æ\8e¥å\88°适当的页面。<br />一个页面如果使用了[[MediaWiki:Disambiguationspage]]内的模板,则会被视为消歧义页。",
+'disambiguations-text' => "以ä¸\8bç\9a\84页é\9d¢é\83½æ\9c\89å\88°'''æ¶\88æ­§ä¹\89页'''ç\9a\84é\93¾æ\8e¥ï¼\8cä½\86å®\83们å\8f¯è\83½å\8f¯ä»¥é\93¾æ\8e¥å\88°æ\9b´适当的页面。<br />一个页面如果使用了[[MediaWiki:Disambiguationspage]]内的模板,则会被视为消歧义页。",
 
 'doubleredirects' => '双重重定向页',
 'doubleredirectstext' => '此页列出了所有重定向到另一重定向页面的页面。每一行都包含有到第一和第二个重定向页面的链接,以及第二个重定向页面的目标——通常就是“真正的”目标页面,亦即是第一个重定向页面应该指向的页面。<del>已划去</del>的为已经解决的项目。',
@@ -2247,6 +2262,8 @@ $UNWATCHURL
 'rollback' => '回退编辑',
 'rollback_short' => '回退',
 'rollbacklink' => '回退',
+'rollbacklinkcount' => '回退$1次编辑',
+'rollbacklinkcount-morethan' => '回退超过$1次的编辑',
 'rollbackfailed' => '回退失败',
 'cantrollback' => '无法恢复编辑。最后的贡献者是本文的唯一作者。',
 'alreadyrolled' => '无法回退[[User:$2|$2]]([[User talk:$2|讨论]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])对[[:$1]]的编辑,其他人已经编辑或者回退了该页。
@@ -3833,5 +3850,5 @@ MediaWiki是基于使用目的而加以发布,然而不负任何担保责任
 'duration-millennia' => '$1千年',
 
 # Unknown messages
-'api-error-filetype-banned-type' => '$1{{PLURAL:$4|不是一个允许的文件类型|不是一个允许的文件类型}}。 允许 {{PLURAL:$3|文件类型是|文件类型是}} $2。',
+'api-error-filetype-banned-type' => '$1{{PLURAL:$4|不是允许的文件类型}}。允许的{{PLURAL:$3|文件类型是|文件类型有}}$2。',
 );
index bc77b16..d2f1f0e 100644 (file)
@@ -475,6 +475,10 @@ $1',
 'youhavenewmessages' => '您有$1($2)。',
 'newmessageslink' => '新訊息',
 'newmessagesdifflink' => '最後更改',
+'youhavenewmessagesfromusers' => '你有來自{{PLURAL:$3| 另一位用戶| $3位用戶}}的$1 ( $2 )。',
+'youhavenewmessagesmanyusers' => '你有來自多位用戶的$1( $2 )。',
+'newmessageslinkplural' => '$1項新訊息',
+'newmessagesdifflinkplural' => '最新$1次更改',
 'youhavenewmessagesmulti' => '您在 $1 有一條新訊息',
 'editsection' => '編輯',
 'editold' => '編輯',
@@ -621,7 +625,7 @@ $2',
 'remembermypassword' => '在這個瀏覽器上記住我的登入資訊(可維持 $1 {{PLURAL:$1|天|天}})',
 'securelogin-stick-https' => '登入後繼續以HTTPS連接',
 'yourdomainname' => '您的網域:',
-'password-change-forbidden' => '您不可更改此 wiki 上的密碼。',
+'password-change-forbidden' => '您不可更改此wiki上的密碼。',
 'externaldberror' => '這可能是由於驗證資料庫錯誤或您被禁止更新您的外部賬號。',
 'login' => '登入',
 'nav-login-createaccount' => '登入/建立新帳號',
@@ -980,6 +984,7 @@ $2
 'expansion-depth-exceeded-warning' => '頁面超出擴展深度',
 'parser-unstrip-loop-warning' => '檢測到迴圈',
 'parser-unstrip-recursion-limit' => '遞歸超過限制 ($1)',
+'converter-manual-rule-error' => '手動語言轉換規則中檢測到錯誤',
 
 # "Undo" feature
 'undo-success' => '該編輯可以被撤銷。請檢查以下對比以核實這正是您想做的,然後儲存以下更改以完成撤銷編輯。',
@@ -1308,7 +1313,7 @@ $1",
 'prefs-memberingroups' => '{{PLURAL:$1|群組}}:',
 'prefs-registration' => '註冊時間:',
 'yourrealname' => '真實姓名:',
-'yourlanguage' => '介面語言:',
+'yourlanguage' => '語言:',
 'yourvariant' => '內容語言變體:',
 'prefs-help-variant' => '您希望用於顯示本站內容的語種或拼寫語系。',
 'yournick' => '新簽名:',
index 6c07858..4356d70 100644 (file)
@@ -8,8 +8,6 @@ CREATE TABLE /*_*/filejournal (
   fj_backend varchar(255) NOT NULL,
   -- The storage path that was affected (may be internal paths)
   fj_path blob NOT NULL,
-  -- SHA-1 file path hash in base-36
-  fj_path_sha1 varbinary(32) NOT NULL default '',
   -- Primitive operation description (create/update/delete)
   fj_op varchar(16) NOT NULL default '',
   -- SHA-1 file content hash in base-36
index 70a9232..2363d40 100644 (file)
@@ -45,6 +45,7 @@ class CopyFileBackend extends Maintenance {
                $this->addOption( 'ratefile', 'File to check periodically for batch size', false, true );
                $this->addOption( 'skiphash', 'Skip SHA-1 sync checks for files' );
                $this->addOption( 'missingonly', 'Only copy files missing from destination listing' );
+               $this->addOption( 'utf8only', 'Skip source files that do not have valid UTF-8 names' );
                $this->setBatchSize( 50 );
        }
 
@@ -56,6 +57,10 @@ class CopyFileBackend extends Maintenance {
 
                $rateFile = $this->getOption( 'ratefile' );
 
+               if ( $this->hasOption( 'utf8only' ) && !extension_loaded( 'mbstring' ) ) {
+                       $this->error( "Cannot check for UTF-8, mbstring extension missing.", 1 ); // die
+               }
+
                $count = 0;
                foreach ( $containers as $container ) {
                        if ( $subDir != '' ) {
@@ -132,7 +137,10 @@ class CopyFileBackend extends Maintenance {
                foreach ( $srcPathsRel as $srcPathRel ) {
                        $srcPath = $src->getRootStoragePath() . "/$backendRel/$srcPathRel";
                        $dstPath = $dst->getRootStoragePath() . "/$backendRel/$srcPathRel";
-                       if ( $this->filesAreSame( $src, $dst, $srcPath, $dstPath ) ) {
+                       if ( $this->hasOption( 'utf8only' ) && !mb_check_encoding( $srcPath, 'UTF-8' ) ) {
+                               $this->error( "Detected illegal (non-UTF8) path for $srcPath." );
+                               continue;
+                       } elseif ( $this->filesAreSame( $src, $dst, $srcPath, $dstPath ) ) {
                                $this->output( "Already have $srcPathRel.\n" );
                                continue; // assume already copied...
                        }
index 11601e6..be4970d 100644 (file)
@@ -306,6 +306,10 @@ $wgMessageStructure = array(
                'youhavenewmessages',
                'newmessageslink',
                'newmessagesdifflink',
+               'youhavenewmessagesfromusers',
+               'youhavenewmessagesmanyusers',
+               'newmessageslinkplural',
+               'newmessagesdifflinkplural',
                'youhavenewmessagesmulti',
                'newtalkseparator',
                'editsection',
index da9fa53..2afd6e0 100644 (file)
@@ -17,6 +17,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  * @author Rob Church <robchur@gmail.com>
  * @licence GNU General Public Licence 2.0 or later
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script that reassigns edits from a user or IP address
+ * to another user.
+ *
+ * @ingroup Maintenance
+ */
 class ReassignEdits extends Maintenance {
        public function __construct() {
                parent::__construct();
index ea07f19..4dbe267 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  */
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script that builds file cache for content pages.
+ *
+ * @ingroup Maintenance
+ */
 class RebuildFileCache extends Maintenance {
        public function __construct() {
                parent::__construct();
@@ -97,7 +103,7 @@ class RebuildFileCache extends Maintenance {
                        foreach ( $res as $row ) {
                                $rebuilt = false;
                                $wgRequestTime = microtime( true ); # bug 22852
-                               
+
                                $wgTitle = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
                                if ( null == $wgTitle ) {
                                        $this->output( "Page {$row->page_id} has bad title\n" );
index 7e3d8c4..936efd7 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Script to update image metadata records
+ * Update image metadata records.
  *
  * Usage: php rebuildImages.php [--missing] [--dry-run]
  * Options:
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script to update image metadata records.
+ *
+ * @ingroup Maintenance
+ */
 class ImageBuilder extends Maintenance {
 
        /**
index ffe66c6..c48fb74 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  */
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script to rebuild the localisation cache.
+ *
+ * @ingroup Maintenance
+ */
 class RebuildLocalisationCache extends Maintenance {
        public function __construct() {
                parent::__construct();
                $this->mDescription = "Rebuild the localisation cache";
                $this->addOption( 'force', 'Rebuild all files, even ones not out of date' );
                $this->addOption( 'threads', 'Fork more than one thread', false, true );
-               $this->addOption( 'outdir', 'Override the output directory (normally $wgCacheDirectory)', 
+               $this->addOption( 'outdir', 'Override the output directory (normally $wgCacheDirectory)',
                        false, true );
        }
 
index dbbed86..bc9f732 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  */
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script that rebuilds link tracking tables from scratch.
+ *
+ * @ingroup Maintenance
+ */
 class RebuildAll extends Maintenance {
        public function __construct() {
                parent::__construct();
index de37da7..a22d3af 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * This script purges all language messages from the cache
+ * Purge all languages from the message cache.
  *
  * 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
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  */
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script that purges all languages from the message cache.
+ *
+ * @ingroup Maintenance
+ */
 class RebuildMessages extends Maintenance {
        public function __construct() {
                parent::__construct();
index 92d6090..5caa0a8 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
- * Rebuild link tracking tables from scratch.  This takes several
- * hours, depending on the database size and server configuration.
+ * Rebuild recent changes from scratch.  This takes several hours,
+ * depending on the database size and server configuration.
  *
  * 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
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  * @todo Document
  */
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script that rebuilds recent changes from scratch.
+ *
+ * @ingroup Maintenance
+ */
 class RebuildRecentchanges extends Maintenance {
        public function __construct() {
                parent::__construct();
index 494e0a7..a37d603 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  * @todo document
  */
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script that rebuilds search index table from scratch.
+ *
+ * @ingroup Maintenance
+ */
 class RebuildTextIndex extends Maintenance {
        const RTI_CHUNK_SIZE = 500;
 
index 958b2ba..6623bea 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Script to refresh image metadata fields. See also rebuildImages.php
+ * Refresh image metadata fields. See also rebuildImages.php
  *
  * Usage: php refreshImageMetadata.php
  *
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script to refresh image metadata fields.
+ *
+ * @ingroup Maintenance
+ */
 class RefreshImageMetadata extends Maintenance {
 
        /**
index b288c2f..490602d 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
  *
+ * @file
  * @ingroup Maintenance
  * @author Rob Church <robchur@gmail.com>
  */
 
 require_once( dirname( __FILE__ ) . '/Maintenance.php' );
 
+/**
+ * Maintenance script that removes unused user accounts from the database.
+ *
+ * @ingroup Maintenance
+ */
 class RemoveUnusedAccounts extends Maintenance {
        public function __construct() {
                parent::__construct();
index 3774ff9..0770682 100644 (file)
@@ -1,31 +1,28 @@
 /*!
- * jQuery JavaScript Library v1.7.2
+ * jQuery JavaScript Library v1.8.0
  * http://jquery.com/
  *
- * Copyright 2011, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
  * Includes Sizzle.js
  * http://sizzlejs.com/
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
  *
- * Date: Wed Mar 21 12:46:34 2012 -0700
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Thu Aug 09 2012 16:24:48 GMT-0400 (Eastern Daylight Time)
  */
 (function( window, undefined ) {
+var
+       // A central reference to the root jQuery(document)
+       rootjQuery,
 
-// Use the correct document accordingly with window argument (sandbox)
-var document = window.document,
-       navigator = window.navigator,
-       location = window.location;
-var jQuery = (function() {
+       // The deferred used on DOM ready
+       readyList,
 
-// Define a local copy of jQuery
-var jQuery = function( selector, context ) {
-               // The jQuery object is actually just the init constructor 'enhanced'
-               return new jQuery.fn.init( selector, context, rootjQuery );
-       },
+       // Use the correct document accordingly with window argument (sandbox)
+       document = window.document,
+       location = window.location,
+       navigator = window.navigator,
 
        // Map over jQuery in case of overwrite
        _jQuery = window.jQuery,
@@ -33,63 +30,64 @@ var jQuery = function( selector, context ) {
        // Map over the $ in case of overwrite
        _$ = window.$,
 
-       // A central reference to the root jQuery(document)
-       rootjQuery,
+       // Save a reference to some core methods
+       core_push = Array.prototype.push,
+       core_slice = Array.prototype.slice,
+       core_indexOf = Array.prototype.indexOf,
+       core_toString = Object.prototype.toString,
+       core_hasOwn = Object.prototype.hasOwnProperty,
+       core_trim = String.prototype.trim,
+
+       // Define a local copy of jQuery
+       jQuery = function( selector, context ) {
+               // The jQuery object is actually just the init constructor 'enhanced'
+               return new jQuery.fn.init( selector, context, rootjQuery );
+       },
 
-       // A simple way to check for HTML strings or ID strings
-       // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
-       quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+       // Used for matching numbers
+       core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
 
-       // Check if a string has a non-whitespace character in it
-       rnotwhite = /\S/,
+       // Used for detecting and trimming whitespace
+       core_rnotwhite = /\S/,
+       core_rspace = /\s+/,
 
-       // Used for trimming whitespace
-       trimLeft = /^\s+/,
-       trimRight = /\s+$/,
+       // IE doesn't match non-breaking spaces with \s
+       rtrim = core_rnotwhite.test("\xA0") ? (/^[\s\xA0]+|[\s\xA0]+$/g) : /^\s+|\s+$/g,
+
+       // A simple way to check for HTML strings
+       // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+       rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
 
        // Match a standalone tag
-       rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+       rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
 
        // JSON RegExp
        rvalidchars = /^[\],:{}\s]*$/,
-       rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
-       rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
        rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
-
-       // Useragent RegExp
-       rwebkit = /(webkit)[ \/]([\w.]+)/,
-       ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
-       rmsie = /(msie) ([\w.]+)/,
-       rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+       rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+       rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
 
        // Matches dashed string for camelizing
-       rdashAlpha = /-([a-z]|[0-9])/ig,
        rmsPrefix = /^-ms-/,
+       rdashAlpha = /-([\da-z])/gi,
 
        // Used by jQuery.camelCase as callback to replace()
        fcamelCase = function( all, letter ) {
                return ( letter + "" ).toUpperCase();
        },
 
-       // Keep a UserAgent string for use with jQuery.browser
-       userAgent = navigator.userAgent,
-
-       // For matching the engine and version of the browser
-       browserMatch,
-
-       // The deferred used on DOM ready
-       readyList,
-
-       // The ready event handler
-       DOMContentLoaded,
-
-       // Save a reference to some core methods
-       toString = Object.prototype.toString,
-       hasOwn = Object.prototype.hasOwnProperty,
-       push = Array.prototype.push,
-       slice = Array.prototype.slice,
-       trim = String.prototype.trim,
-       indexOf = Array.prototype.indexOf,
+       // The ready event handler and self cleanup method
+       DOMContentLoaded = function() {
+               if ( document.addEventListener ) {
+                       document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+                       jQuery.ready();
+               } else if ( document.readyState === "complete" ) {
+                       // we're here because readyState === "complete" in oldIE
+                       // which is good enough for us to call the dom ready!
+                       document.detachEvent( "onreadystatechange", DOMContentLoaded );
+                       jQuery.ready();
+               }
+       },
 
        // [[Class]] -> type pairs
        class2type = {};
@@ -99,7 +97,7 @@ jQuery.fn = jQuery.prototype = {
        init: function( selector, context, rootjQuery ) {
                var match, elem, ret, doc;
 
-               // Handle $(""), $(null), or $(undefined)
+               // Handle $(""), $(null), $(undefined), $(false)
                if ( !selector ) {
                        return this;
                }
@@ -111,55 +109,33 @@ jQuery.fn = jQuery.prototype = {
                        return this;
                }
 
-               // The body element only exists once, optimize finding it
-               if ( selector === "body" && !context && document.body ) {
-                       this.context = document;
-                       this[0] = document.body;
-                       this.selector = selector;
-                       this.length = 1;
-                       return this;
-               }
-
                // Handle HTML strings
                if ( typeof selector === "string" ) {
-                       // Are we dealing with HTML string or an ID?
                        if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
                                // Assume that strings that start and end with <> are HTML and skip the regex check
                                match = [ null, selector, null ];
 
                        } else {
-                               match = quickExpr.exec( selector );
+                               match = rquickExpr.exec( selector );
                        }
 
-                       // Verify a match, and that no context was specified for #id
+                       // Match html or make sure no context is specified for #id
                        if ( match && (match[1] || !context) ) {
 
                                // HANDLE: $(html) -> $(array)
                                if ( match[1] ) {
                                        context = context instanceof jQuery ? context[0] : context;
-                                       doc = ( context ? context.ownerDocument || context : document );
-
-                                       // If a single string is passed in and it's a single tag
-                                       // just do a createElement and skip the rest
-                                       ret = rsingleTag.exec( selector );
-
-                                       if ( ret ) {
-                                               if ( jQuery.isPlainObject( context ) ) {
-                                                       selector = [ document.createElement( ret[1] ) ];
-                                                       jQuery.fn.attr.call( selector, context, true );
-
-                                               } else {
-                                                       selector = [ doc.createElement( ret[1] ) ];
-                                               }
+                                       doc = ( context && context.nodeType ? context.ownerDocument || context : document );
 
-                                       } else {
-                                               ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
-                                               selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+                                       // scripts is true for back-compat
+                                       selector = jQuery.parseHTML( match[1], doc, true );
+                                       if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+                                               this.attr.call( selector, context, true );
                                        }
 
                                        return jQuery.merge( this, selector );
 
-                               // HANDLE: $("#id")
+                               // HANDLE: $(#id)
                                } else {
                                        elem = document.getElementById( match[2] );
 
@@ -210,7 +186,7 @@ jQuery.fn = jQuery.prototype = {
        selector: "",
 
        // The current version of jQuery being used
-       jquery: "1.7.2",
+       jquery: "1.8.0",
 
        // The default length of a jQuery object is 0
        length: 0,
@@ -221,7 +197,7 @@ jQuery.fn = jQuery.prototype = {
        },
 
        toArray: function() {
-               return slice.call( this, 0 );
+               return core_slice.call( this );
        },
 
        // Get the Nth element in the matched element set OR
@@ -239,15 +215,9 @@ jQuery.fn = jQuery.prototype = {
        // Take an array of elements and push it onto the stack
        // (returning the new matched element set)
        pushStack: function( elems, name, selector ) {
-               // Build a new jQuery matched element set
-               var ret = this.constructor();
 
-               if ( jQuery.isArray( elems ) ) {
-                       push.apply( ret, elems );
-
-               } else {
-                       jQuery.merge( ret, elems );
-               }
+               // Build a new jQuery matched element set
+               var ret = jQuery.merge( this.constructor(), elems );
 
                // Add the old object onto the stack (as a reference)
                ret.prevObject = this;
@@ -272,11 +242,8 @@ jQuery.fn = jQuery.prototype = {
        },
 
        ready: function( fn ) {
-               // Attach the listeners
-               jQuery.bindReady();
-
                // Add the callback
-               readyList.add( fn );
+               jQuery.ready.promise().done( fn );
 
                return this;
        },
@@ -297,8 +264,8 @@ jQuery.fn = jQuery.prototype = {
        },
 
        slice: function() {
-               return this.pushStack( slice.apply( this, arguments ),
-                       "slice", slice.call(arguments).join(",") );
+               return this.pushStack( core_slice.apply( this, arguments ),
+                       "slice", core_slice.call(arguments).join(",") );
        },
 
        map: function( callback ) {
@@ -313,7 +280,7 @@ jQuery.fn = jQuery.prototype = {
 
        // For internal use only.
        // Behaves like an Array's method, not like a jQuery method.
-       push: push,
+       push: core_push,
        sort: [].sort,
        splice: [].splice
 };
@@ -416,73 +383,31 @@ jQuery.extend({
 
        // Handle when the DOM is ready
        ready: function( wait ) {
-               // Either a released hold or an DOMready/load event and not yet ready
-               if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
-                       // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
-                       if ( !document.body ) {
-                               return setTimeout( jQuery.ready, 1 );
-                       }
-
-                       // Remember that the DOM is ready
-                       jQuery.isReady = true;
-
-                       // If a normal DOM Ready event fired, decrement, and wait if need be
-                       if ( wait !== true && --jQuery.readyWait > 0 ) {
-                               return;
-                       }
-
-                       // If there are functions bound, to execute
-                       readyList.fireWith( document, [ jQuery ] );
-
-                       // Trigger any bound ready events
-                       if ( jQuery.fn.trigger ) {
-                               jQuery( document ).trigger( "ready" ).off( "ready" );
-                       }
-               }
-       },
 
-       bindReady: function() {
-               if ( readyList ) {
+               // Abort if there are pending holds or we're already ready
+               if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
                        return;
                }
 
-               readyList = jQuery.Callbacks( "once memory" );
-
-               // Catch cases where $(document).ready() is called after the
-               // browser event has already occurred.
-               if ( document.readyState === "complete" ) {
-                       // Handle it asynchronously to allow scripts the opportunity to delay ready
+               // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+               if ( !document.body ) {
                        return setTimeout( jQuery.ready, 1 );
                }
 
-               // Mozilla, Opera and webkit nightlies currently support this event
-               if ( document.addEventListener ) {
-                       // Use the handy event callback
-                       document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-
-                       // A fallback to window.onload, that will always work
-                       window.addEventListener( "load", jQuery.ready, false );
-
-               // If IE event model is used
-               } else if ( document.attachEvent ) {
-                       // ensure firing before onload,
-                       // maybe late but safe also for iframes
-                       document.attachEvent( "onreadystatechange", DOMContentLoaded );
-
-                       // A fallback to window.onload, that will always work
-                       window.attachEvent( "onload", jQuery.ready );
+               // Remember that the DOM is ready
+               jQuery.isReady = true;
 
-                       // If IE and not a frame
-                       // continually check to see if the document is ready
-                       var toplevel = false;
+               // If a normal DOM Ready event fired, decrement, and wait if need be
+               if ( wait !== true && --jQuery.readyWait > 0 ) {
+                       return;
+               }
 
-                       try {
-                               toplevel = window.frameElement == null;
-                       } catch(e) {}
+               // If there are functions bound, to execute
+               readyList.resolveWith( document, [ jQuery ] );
 
-                       if ( document.documentElement.doScroll && toplevel ) {
-                               doScrollCheck();
-                       }
+               // Trigger any bound ready events
+               if ( jQuery.fn.trigger ) {
+                       jQuery( document ).trigger("ready").off("ready");
                }
        },
 
@@ -508,7 +433,7 @@ jQuery.extend({
        type: function( obj ) {
                return obj == null ?
                        String( obj ) :
-                       class2type[ toString.call(obj) ] || "object";
+                       class2type[ core_toString.call(obj) ] || "object";
        },
 
        isPlainObject: function( obj ) {
@@ -522,8 +447,8 @@ jQuery.extend({
                try {
                        // Not own constructor property must be Object
                        if ( obj.constructor &&
-                               !hasOwn.call(obj, "constructor") &&
-                               !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+                               !core_hasOwn.call(obj, "constructor") &&
+                               !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
                                return false;
                        }
                } catch ( e ) {
@@ -537,11 +462,12 @@ jQuery.extend({
                var key;
                for ( key in obj ) {}
 
-               return key === undefined || hasOwn.call( obj, key );
+               return key === undefined || core_hasOwn.call( obj, key );
        },
 
        isEmptyObject: function( obj ) {
-               for ( var name in obj ) {
+               var name;
+               for ( name in obj ) {
                        return false;
                }
                return true;
@@ -551,8 +477,32 @@ jQuery.extend({
                throw new Error( msg );
        },
 
+       // data: string of html
+       // context (optional): If specified, the fragment will be created in this context, defaults to document
+       // scripts (optional): If true, will include scripts passed in the html string
+       parseHTML: function( data, context, scripts ) {
+               var parsed;
+               if ( !data || typeof data !== "string" ) {
+                       return null;
+               }
+               if ( typeof context === "boolean" ) {
+                       scripts = context;
+                       context = 0;
+               }
+               context = context || document;
+
+               // Single tag
+               if ( (parsed = rsingleTag.exec( data )) ) {
+                       return [ context.createElement( parsed[1] ) ];
+               }
+
+               parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+               return jQuery.merge( [],
+                       (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+       },
+
        parseJSON: function( data ) {
-               if ( typeof data !== "string" || !data ) {
+               if ( !data || typeof data !== "string") {
                        return null;
                }
 
@@ -578,10 +528,10 @@ jQuery.extend({
 
        // Cross-browser xml parsing
        parseXML: function( data ) {
-               if ( typeof data !== "string" || !data ) {
+               var xml, tmp;
+               if ( !data || typeof data !== "string" ) {
                        return null;
                }
-               var xml, tmp;
                try {
                        if ( window.DOMParser ) { // Standard
                                tmp = new DOMParser();
@@ -606,7 +556,7 @@ jQuery.extend({
        // Workarounds based on findings by Jim Driscoll
        // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
        globalEval: function( data ) {
-               if ( data && rnotwhite.test( data ) ) {
+               if ( data && core_rnotwhite.test( data ) ) {
                        // We use execScript on Internet Explorer
                        // We use an anonymous function so that context is window
                        // rather than jQuery in Firefox
@@ -627,21 +577,22 @@ jQuery.extend({
        },
 
        // args is for internal usage only
-       each: function( object, callback, args ) {
-               var name, i = 0,
-                       length = object.length,
-                       isObj = length === undefined || jQuery.isFunction( object );
+       each: function( obj, callback, args ) {
+               var name,
+                       i = 0,
+                       length = obj.length,
+                       isObj = length === undefined || jQuery.isFunction( obj );
 
                if ( args ) {
                        if ( isObj ) {
-                               for ( name in object ) {
-                                       if ( callback.apply( object[ name ], args ) === false ) {
+                               for ( name in obj ) {
+                                       if ( callback.apply( obj[ name ], args ) === false ) {
                                                break;
                                        }
                                }
                        } else {
                                for ( ; i < length; ) {
-                                       if ( callback.apply( object[ i++ ], args ) === false ) {
+                                       if ( callback.apply( obj[ i++ ], args ) === false ) {
                                                break;
                                        }
                                }
@@ -650,71 +601,72 @@ jQuery.extend({
                // A special, fast, case for the most common use of each
                } else {
                        if ( isObj ) {
-                               for ( name in object ) {
-                                       if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+                               for ( name in obj ) {
+                                       if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
                                                break;
                                        }
                                }
                        } else {
                                for ( ; i < length; ) {
-                                       if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+                                       if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
                                                break;
                                        }
                                }
                        }
                }
 
-               return object;
+               return obj;
        },
 
        // Use native String.trim function wherever possible
-       trim: trim ?
+       trim: core_trim ?
                function( text ) {
                        return text == null ?
                                "" :
-                               trim.call( text );
+                               core_trim.call( text );
                } :
 
                // Otherwise use our own trimming functionality
                function( text ) {
                        return text == null ?
                                "" :
-                               text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+                               text.toString().replace( rtrim, "" );
                },
 
        // results is for internal usage only
-       makeArray: function( array, results ) {
-               var ret = results || [];
+       makeArray: function( arr, results ) {
+               var type,
+                       ret = results || [];
 
-               if ( array != null ) {
+               if ( arr != null ) {
                        // The window, strings (and functions) also have 'length'
                        // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
-                       var type = jQuery.type( array );
+                       type = jQuery.type( arr );
 
-                       if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
-                               push.call( ret, array );
+                       if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+                               core_push.call( ret, arr );
                        } else {
-                               jQuery.merge( ret, array );
+                               jQuery.merge( ret, arr );
                        }
                }
 
                return ret;
        },
 
-       inArray: function( elem, array, i ) {
+       inArray: function( elem, arr, i ) {
                var len;
 
-               if ( array ) {
-                       if ( indexOf ) {
-                               return indexOf.call( array, elem, i );
+               if ( arr ) {
+                       if ( core_indexOf ) {
+                               return core_indexOf.call( arr, elem, i );
                        }
 
-                       len = array.length;
+                       len = arr.length;
                        i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
 
                        for ( ; i < len; i++ ) {
                                // Skip accessing in sparse arrays
-                               if ( i in array && array[ i ] === elem ) {
+                               if ( i in arr && arr[ i ] === elem ) {
                                        return i;
                                }
                        }
@@ -724,11 +676,12 @@ jQuery.extend({
        },
 
        merge: function( first, second ) {
-               var i = first.length,
+               var l = second.length,
+                       i = first.length,
                        j = 0;
 
-               if ( typeof second.length === "number" ) {
-                       for ( var l = second.length; j < l; j++ ) {
+               if ( typeof l === "number" ) {
+                       for ( ; j < l; j++ ) {
                                first[ i++ ] = second[ j ];
                        }
 
@@ -744,12 +697,15 @@ jQuery.extend({
        },
 
        grep: function( elems, callback, inv ) {
-               var ret = [], retVal;
+               var retVal,
+                       ret = [],
+                       i = 0,
+                       length = elems.length;
                inv = !!inv;
 
                // Go through the array, only saving the items
                // that pass the validator function
-               for ( var i = 0, length = elems.length; i < length; i++ ) {
+               for ( ; i < length; i++ ) {
                        retVal = !!callback( elems[ i ], i );
                        if ( inv !== retVal ) {
                                ret.push( elems[ i ] );
@@ -761,7 +717,8 @@ jQuery.extend({
 
        // arg is for internal usage only
        map: function( elems, callback, arg ) {
-               var value, key, ret = [],
+               var value, key,
+                       ret = [],
                        i = 0,
                        length = elems.length,
                        // jquery objects are treated as arrays
@@ -798,8 +755,10 @@ jQuery.extend({
        // Bind a function to a context, optionally partially applying any
        // arguments.
        proxy: function( fn, context ) {
+               var tmp, args, proxy;
+
                if ( typeof context === "string" ) {
-                       var tmp = fn[ context ];
+                       tmp = fn[ context ];
                        context = fn;
                        fn = tmp;
                }
@@ -811,10 +770,10 @@ jQuery.extend({
                }
 
                // Simulated bind
-               var args = slice.call( arguments, 2 ),
-                       proxy = function() {
-                               return fn.apply( context, args.concat( slice.call( arguments ) ) );
-                       };
+               args = core_slice.call( arguments, 2 );
+               proxy = function() {
+                       return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+               };
 
                // Set the guid of unique handler to the same of original handler, so it can be removed
                proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
@@ -822,7 +781,7 @@ jQuery.extend({
                return proxy;
        },
 
-       // Mutifunctional method to get and set values to a collection
+       // Multifunctional method to get and set values of a collection
        // The value/s can optionally be executed if it's a function
        access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
                var exec,
@@ -877,136 +836,95 @@ jQuery.extend({
 
        now: function() {
                return ( new Date() ).getTime();
-       },
-
-       // Use of jQuery.browser is frowned upon.
-       // More details: http://docs.jquery.com/Utilities/jQuery.browser
-       uaMatch: function( ua ) {
-               ua = ua.toLowerCase();
+       }
+});
 
-               var match = rwebkit.exec( ua ) ||
-                       ropera.exec( ua ) ||
-                       rmsie.exec( ua ) ||
-                       ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
-                       [];
+jQuery.ready.promise = function( obj ) {
+       if ( !readyList ) {
 
-               return { browser: match[1] || "", version: match[2] || "0" };
-       },
+               readyList = jQuery.Deferred();
 
-       sub: function() {
-               function jQuerySub( selector, context ) {
-                       return new jQuerySub.fn.init( selector, context );
-               }
-               jQuery.extend( true, jQuerySub, this );
-               jQuerySub.superclass = this;
-               jQuerySub.fn = jQuerySub.prototype = this();
-               jQuerySub.fn.constructor = jQuerySub;
-               jQuerySub.sub = this.sub;
-               jQuerySub.fn.init = function init( selector, context ) {
-                       if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
-                               context = jQuerySub( context );
-                       }
+               // Catch cases where $(document).ready() is called after the
+               // browser event has already occurred.
+               if ( document.readyState === "complete" || ( document.readyState !== "loading" && document.addEventListener ) ) {
+                       // Handle it asynchronously to allow scripts the opportunity to delay ready
+                       setTimeout( jQuery.ready, 1 );
 
-                       return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
-               };
-               jQuerySub.fn.init.prototype = jQuerySub.fn;
-               var rootjQuerySub = jQuerySub(document);
-               return jQuerySub;
-       },
+               // Standards-based browsers support DOMContentLoaded
+               } else if ( document.addEventListener ) {
+                       // Use the handy event callback
+                       document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
 
-       browser: {}
-});
+                       // A fallback to window.onload, that will always work
+                       window.addEventListener( "load", jQuery.ready, false );
 
-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
-       class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
+               // If IE event model is used
+               } else {
+                       // Ensure firing before onload, maybe late but safe also for iframes
+                       document.attachEvent( "onreadystatechange", DOMContentLoaded );
 
-browserMatch = jQuery.uaMatch( userAgent );
-if ( browserMatch.browser ) {
-       jQuery.browser[ browserMatch.browser ] = true;
-       jQuery.browser.version = browserMatch.version;
-}
+                       // A fallback to window.onload, that will always work
+                       window.attachEvent( "onload", jQuery.ready );
 
-// Deprecated, use jQuery.browser.webkit instead
-if ( jQuery.browser.webkit ) {
-       jQuery.browser.safari = true;
-}
+                       // If IE and not a frame
+                       // continually check to see if the document is ready
+                       var top = false;
 
-// IE doesn't match non-breaking spaces with \s
-if ( rnotwhite.test( "\xA0" ) ) {
-       trimLeft = /^[\s\xA0]+/;
-       trimRight = /[\s\xA0]+$/;
-}
+                       try {
+                               top = window.frameElement == null && document.documentElement;
+                       } catch(e) {}
 
-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
+                       if ( top && top.doScroll ) {
+                               (function doScrollCheck() {
+                                       if ( !jQuery.isReady ) {
 
-// Cleanup functions for the document ready method
-if ( document.addEventListener ) {
-       DOMContentLoaded = function() {
-               document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-               jQuery.ready();
-       };
+                                               try {
+                                                       // Use the trick by Diego Perini
+                                                       // http://javascript.nwbox.com/IEContentLoaded/
+                                                       top.doScroll("left");
+                                               } catch(e) {
+                                                       return setTimeout( doScrollCheck, 50 );
+                                               }
 
-} else if ( document.attachEvent ) {
-       DOMContentLoaded = function() {
-               // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
-               if ( document.readyState === "complete" ) {
-                       document.detachEvent( "onreadystatechange", DOMContentLoaded );
-                       jQuery.ready();
+                                               // and execute any waiting functions
+                                               jQuery.ready();
+                                       }
+                               })();
+                       }
                }
-       };
-}
-
-// The DOM ready check for Internet Explorer
-function doScrollCheck() {
-       if ( jQuery.isReady ) {
-               return;
-       }
-
-       try {
-               // If IE is used, use the trick by Diego Perini
-               // http://javascript.nwbox.com/IEContentLoaded/
-               document.documentElement.doScroll("left");
-       } catch(e) {
-               setTimeout( doScrollCheck, 1 );
-               return;
        }
+       return readyList.promise( obj );
+};
 
-       // and execute any waiting functions
-       jQuery.ready();
-}
-
-return jQuery;
-
-})();
-
-
-// String to Object flags format cache
-var flagsCache = {};
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+       class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
 
-// Convert String-formatted flags into Object-formatted ones and store in cache
-function createFlags( flags ) {
-       var object = flagsCache[ flags ] = {},
-               i, length;
-       flags = flags.split( /\s+/ );
-       for ( i = 0, length = flags.length; i < length; i++ ) {
-               object[ flags[i] ] = true;
-       }
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+       var object = optionsCache[ options ] = {};
+       jQuery.each( options.split( core_rspace ), function( _, flag ) {
+               object[ flag ] = true;
+       });
        return object;
 }
 
 /*
  * Create a callback list using the following parameters:
  *
- *     flags:  an optional list of space-separated flags that will change how
- *                     the callback list behaves
+ *     options: an optional list of space-separated options that will change how
+ *                     the callback list behaves or a more traditional option object
  *
  * By default a callback list will act like an event callback list and can be
  * "fired" multiple times.
  *
- * Possible flags:
+ * Possible options:
  *
  *     once:                   will ensure the callback list can only be fired once (like a Deferred)
  *
@@ -1019,17 +937,15 @@ function createFlags( flags ) {
  *     stopOnFalse:    interrupt callings when a callback returns false
  *
  */
-jQuery.Callbacks = function( flags ) {
+jQuery.Callbacks = function( options ) {
 
-       // Convert flags from String-formatted to Object-formatted
+       // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
-       flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+       options = typeof options === "string" ?
+               ( optionsCache[ options ] || createOptions( options ) ) :
+               jQuery.extend( {}, options );
 
-       var // Actual callback list
-               list = [],
-               // Stack of fire calls for repeatable lists
-               stack = [],
-               // Last fire value (for non-forgettable lists)
+       var // Last fire value (for non-forgettable lists)
                memory,
                // Flag to know if list was already fired
                fired,
@@ -1041,53 +957,34 @@ jQuery.Callbacks = function( flags ) {
                firingLength,
                // Index of currently firing callback (modified by remove if needed)
                firingIndex,
-               // Add one or several callbacks to the list
-               add = function( args ) {
-                       var i,
-                               length,
-                               elem,
-                               type,
-                               actual;
-                       for ( i = 0, length = args.length; i < length; i++ ) {
-                               elem = args[ i ];
-                               type = jQuery.type( elem );
-                               if ( type === "array" ) {
-                                       // Inspect recursively
-                                       add( elem );
-                               } else if ( type === "function" ) {
-                                       // Add if not in unique mode and callback is not in
-                                       if ( !flags.unique || !self.has( elem ) ) {
-                                               list.push( elem );
-                                       }
-                               }
-                       }
-               },
+               // Actual callback list
+               list = [],
+               // Stack of fire calls for repeatable lists
+               stack = !options.once && [],
                // Fire callbacks
-               fire = function( context, args ) {
-                       args = args || [];
-                       memory = !flags.memory || [ context, args ];
+               fire = function( data ) {
+                       memory = options.memory && data;
                        fired = true;
-                       firing = true;
                        firingIndex = firingStart || 0;
                        firingStart = 0;
                        firingLength = list.length;
+                       firing = true;
                        for ( ; list && firingIndex < firingLength; firingIndex++ ) {
-                               if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
-                                       memory = true; // Mark as halted
+                               if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+                                       memory = false; // To prevent further calls using add
                                        break;
                                }
                        }
                        firing = false;
                        if ( list ) {
-                               if ( !flags.once ) {
-                                       if ( stack && stack.length ) {
-                                               memory = stack.shift();
-                                               self.fireWith( memory[ 0 ], memory[ 1 ] );
+                               if ( stack ) {
+                                       if ( stack.length ) {
+                                               fire( stack.shift() );
                                        }
-                               } else if ( memory === true ) {
-                                       self.disable();
-                               } else {
+                               } else if ( memory ) {
                                        list = [];
+                               } else {
+                                       self.disable();
                                }
                        }
                },
@@ -1096,18 +993,27 @@ jQuery.Callbacks = function( flags ) {
                        // Add a callback or a collection of callbacks to the list
                        add: function() {
                                if ( list ) {
-                                       var length = list.length;
-                                       add( arguments );
+                                       // First, we save the current length
+                                       var start = list.length;
+                                       (function add( args ) {
+                                               jQuery.each( args, function( _, arg ) {
+                                                       if ( jQuery.isFunction( arg ) && ( !options.unique || !self.has( arg ) ) ) {
+                                                               list.push( arg );
+                                                       } else if ( arg && arg.length ) {
+                                                               // Inspect recursively
+                                                               add( arg );
+                                                       }
+                                               });
+                                       })( arguments );
                                        // Do we need to add the callbacks to the
                                        // current firing batch?
                                        if ( firing ) {
                                                firingLength = list.length;
                                        // With memory, if we're not firing then
-                                       // we should call right away, unless previous
-                                       // firing was halted (stopOnFalse)
-                                       } else if ( memory && memory !== true ) {
-                                               firingStart = length;
-                                               fire( memory[ 0 ], memory[ 1 ] );
+                                       // we should call right away
+                                       } else if ( memory ) {
+                                               firingStart = start;
+                                               fire( memory );
                                        }
                                }
                                return this;
@@ -1115,46 +1021,27 @@ jQuery.Callbacks = function( flags ) {
                        // Remove a callback from the list
                        remove: function() {
                                if ( list ) {
-                                       var args = arguments,
-                                               argIndex = 0,
-                                               argLength = args.length;
-                                       for ( ; argIndex < argLength ; argIndex++ ) {
-                                               for ( var i = 0; i < list.length; i++ ) {
-                                                       if ( args[ argIndex ] === list[ i ] ) {
-                                                               // Handle firingIndex and firingLength
-                                                               if ( firing ) {
-                                                                       if ( i <= firingLength ) {
-                                                                               firingLength--;
-                                                                               if ( i <= firingIndex ) {
-                                                                                       firingIndex--;
-                                                                               }
-                                                                       }
+                                       jQuery.each( arguments, function( _, arg ) {
+                                               var index;
+                                               while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+                                                       list.splice( index, 1 );
+                                                       // Handle firing indexes
+                                                       if ( firing ) {
+                                                               if ( index <= firingLength ) {
+                                                                       firingLength--;
                                                                }
-                                                               // Remove the element
-                                                               list.splice( i--, 1 );
-                                                               // If we have some unicity property then
-                                                               // we only need to do this once
-                                                               if ( flags.unique ) {
-                                                                       break;
+                                                               if ( index <= firingIndex ) {
+                                                                       firingIndex--;
                                                                }
                                                        }
                                                }
-                                       }
+                                       });
                                }
                                return this;
                        },
                        // Control if a given callback is in the list
                        has: function( fn ) {
-                               if ( list ) {
-                                       var i = 0,
-                                               length = list.length;
-                                       for ( ; i < length; i++ ) {
-                                               if ( fn === list[ i ] ) {
-                                                       return true;
-                                               }
-                                       }
-                               }
-                               return false;
+                               return jQuery.inArray( fn, list ) > -1;
                        },
                        // Remove all callbacks from the list
                        empty: function() {
@@ -1173,7 +1060,7 @@ jQuery.Callbacks = function( flags ) {
                        // Lock the list in its current state
                        lock: function() {
                                stack = undefined;
-                               if ( !memory || memory === true ) {
+                               if ( !memory ) {
                                        self.disable();
                                }
                                return this;
@@ -1184,13 +1071,13 @@ jQuery.Callbacks = function( flags ) {
                        },
                        // Call all callbacks with the given context and arguments
                        fireWith: function( context, args ) {
-                               if ( stack ) {
+                               args = args || [];
+                               args = [ context, args.slice ? args.slice() : args ];
+                               if ( list && ( !fired || stack ) ) {
                                        if ( firing ) {
-                                               if ( !flags.once ) {
-                                                       stack.push( [ context, args ] );
-                                               }
-                                       } else if ( !( flags.once && memory ) ) {
-                                               fire( context, args );
+                                               stack.push( args );
+                                       } else {
+                                               fire( args );
                                        }
                                }
                                return this;
@@ -1208,98 +1095,85 @@ jQuery.Callbacks = function( flags ) {
 
        return self;
 };
-
-
-
-
-var // Static reference to slice
-       sliceDeferred = [].slice;
-
 jQuery.extend({
 
        Deferred: function( func ) {
-               var doneList = jQuery.Callbacks( "once memory" ),
-                       failList = jQuery.Callbacks( "once memory" ),
-                       progressList = jQuery.Callbacks( "memory" ),
+               var tuples = [
+                               // action, add listener, listener list, final state
+                               [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+                               [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+                               [ "notify", "progress", jQuery.Callbacks("memory") ]
+                       ],
                        state = "pending",
-                       lists = {
-                               resolve: doneList,
-                               reject: failList,
-                               notify: progressList
-                       },
                        promise = {
-                               done: doneList.add,
-                               fail: failList.add,
-                               progress: progressList.add,
-
                                state: function() {
                                        return state;
                                },
-
-                               // Deprecated
-                               isResolved: doneList.fired,
-                               isRejected: failList.fired,
-
-                               then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
-                                       deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
-                                       return this;
-                               },
                                always: function() {
-                                       deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+                                       deferred.done( arguments ).fail( arguments );
                                        return this;
                                },
-                               pipe: function( fnDone, fnFail, fnProgress ) {
+                               then: function( /* fnDone, fnFail, fnProgress */ ) {
+                                       var fns = arguments;
                                        return jQuery.Deferred(function( newDefer ) {
-                                               jQuery.each( {
-                                                       done: [ fnDone, "resolve" ],
-                                                       fail: [ fnFail, "reject" ],
-                                                       progress: [ fnProgress, "notify" ]
-                                               }, function( handler, data ) {
-                                                       var fn = data[ 0 ],
-                                                               action = data[ 1 ],
-                                                               returned;
-                                                       if ( jQuery.isFunction( fn ) ) {
-                                                               deferred[ handler ](function() {
-                                                                       returned = fn.apply( this, arguments );
+                                               jQuery.each( tuples, function( i, tuple ) {
+                                                       var action = tuple[ 0 ],
+                                                               fn = fns[ i ];
+                                                       // deferred[ done | fail | progress ] for forwarding actions to newDefer
+                                                       deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+                                                               function() {
+                                                                       var returned = fn.apply( this, arguments );
                                                                        if ( returned && jQuery.isFunction( returned.promise ) ) {
-                                                                               returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+                                                                               returned.promise()
+                                                                                       .done( newDefer.resolve )
+                                                                                       .fail( newDefer.reject )
+                                                                                       .progress( newDefer.notify );
                                                                        } else {
                                                                                newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
                                                                        }
-                                                               });
-                                                       } else {
-                                                               deferred[ handler ]( newDefer[ action ] );
-                                                       }
+                                                               } :
+                                                               newDefer[ action ]
+                                                       );
                                                });
+                                               fns = null;
                                        }).promise();
                                },
                                // Get a promise for this deferred
                                // If obj is provided, the promise aspect is added to the object
                                promise: function( obj ) {
-                                       if ( obj == null ) {
-                                               obj = promise;
-                                       } else {
-                                               for ( var key in promise ) {
-                                                       obj[ key ] = promise[ key ];
-                                               }
-                                       }
-                                       return obj;
+                                       return typeof obj === "object" ? jQuery.extend( obj, promise ) : promise;
                                }
                        },
-                       deferred = promise.promise({}),
-                       key;
+                       deferred = {};
 
-               for ( key in lists ) {
-                       deferred[ key ] = lists[ key ].fire;
-                       deferred[ key + "With" ] = lists[ key ].fireWith;
-               }
+               // Keep pipe for back-compat
+               promise.pipe = promise.then;
+
+               // Add list-specific methods
+               jQuery.each( tuples, function( i, tuple ) {
+                       var list = tuple[ 2 ],
+                               stateString = tuple[ 3 ];
+
+                       // promise[ done | fail | progress ] = list.add
+                       promise[ tuple[1] ] = list.add;
+
+                       // Handle state
+                       if ( stateString ) {
+                               list.add(function() {
+                                       // state = [ resolved | rejected ]
+                                       state = stateString;
 
-               // Handle state
-               deferred.done( function() {
-                       state = "resolved";
-               }, failList.disable, progressList.lock ).fail( function() {
-                       state = "rejected";
-               }, doneList.disable, progressList.lock );
+                               // [ reject_list | resolve_list ].disable; progress_list.lock
+                               }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+                       }
+
+                       // deferred[ resolve | reject | notify ] = list.fire
+                       deferred[ tuple[0] ] = list.fire;
+                       deferred[ tuple[0] + "With" ] = list.fireWith;
+               });
+
+               // Make the deferred a promise
+               promise.promise( deferred );
 
                // Call given func if any
                if ( func ) {
@@ -1311,52 +1185,57 @@ jQuery.extend({
        },
 
        // Deferred helper
-       when: function( firstParam ) {
-               var args = sliceDeferred.call( arguments, 0 ),
-                       i = 0,
-                       length = args.length,
-                       pValues = new Array( length ),
-                       count = length,
-                       pCount = length,
-                       deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
-                               firstParam :
-                               jQuery.Deferred(),
-                       promise = deferred.promise();
-               function resolveFunc( i ) {
-                       return function( value ) {
-                               args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
-                               if ( !( --count ) ) {
-                                       deferred.resolveWith( deferred, args );
-                               }
-                       };
-               }
-               function progressFunc( i ) {
-                       return function( value ) {
-                               pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
-                               deferred.notifyWith( promise, pValues );
-                       };
-               }
+       when: function( subordinate /* , ..., subordinateN */ ) {
+               var i = 0,
+                       resolveValues = core_slice.call( arguments ),
+                       length = resolveValues.length,
+
+                       // the count of uncompleted subordinates
+                       remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+                       // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+                       deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+                       // Update function for both resolve and progress values
+                       updateFunc = function( i, contexts, values ) {
+                               return function( value ) {
+                                       contexts[ i ] = this;
+                                       values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+                                       if( values === progressValues ) {
+                                               deferred.notifyWith( contexts, values );
+                                       } else if ( !( --remaining ) ) {
+                                               deferred.resolveWith( contexts, values );
+                                       }
+                               };
+                       },
+
+                       progressValues, progressContexts, resolveContexts;
+
+               // add listeners to Deferred subordinates; treat others as resolved
                if ( length > 1 ) {
+                       progressValues = new Array( length );
+                       progressContexts = new Array( length );
+                       resolveContexts = new Array( length );
                        for ( ; i < length; i++ ) {
-                               if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
-                                       args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+                               if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+                                       resolveValues[ i ].promise()
+                                               .done( updateFunc( i, resolveContexts, resolveValues ) )
+                                               .fail( deferred.reject )
+                                               .progress( updateFunc( i, progressContexts, progressValues ) );
                                } else {
-                                       --count;
+                                       --remaining;
                                }
                        }
-                       if ( !count ) {
-                               deferred.resolveWith( deferred, args );
-                       }
-               } else if ( deferred !== firstParam ) {
-                       deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
                }
-               return promise;
-       }
-});
-
-
 
+               // if we're not waiting on anything, resolve the master
+               if ( !remaining ) {
+                       deferred.resolveWith( resolveContexts, resolveValues );
+               }
 
+               return deferred.promise();
+       }
+});
 jQuery.support = (function() {
 
        var support,
@@ -1366,20 +1245,19 @@ jQuery.support = (function() {
                opt,
                input,
                fragment,
-               tds,
-               events,
                eventName,
                i,
                isSupported,
-               div = document.createElement( "div" ),
-               documentElement = document.documentElement;
+               clickFn,
+               div = document.createElement("div");
 
        // Preliminary tests
-       div.setAttribute("className", "t");
-       div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+       div.setAttribute( "className", "t" );
+       div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
 
-       all = div.getElementsByTagName( "*" );
-       a = div.getElementsByTagName( "a" )[ 0 ];
+       all = div.getElementsByTagName("*");
+       a = div.getElementsByTagName("a")[ 0 ];
+       a.style.cssText = "top:1px;float:left;opacity:.5";
 
        // Can't get basic test support
        if ( !all || !all.length || !a ) {
@@ -1387,9 +1265,9 @@ jQuery.support = (function() {
        }
 
        // First batch of supports tests
-       select = document.createElement( "select" );
+       select = document.createElement("select");
        opt = select.appendChild( document.createElement("option") );
-       input = div.getElementsByTagName( "input" )[ 0 ];
+       input = div.getElementsByTagName("input")[ 0 ];
 
        support = {
                // IE strips leading whitespace when .innerHTML is used
@@ -1414,7 +1292,7 @@ jQuery.support = (function() {
                // Make sure that element opacity exists
                // (IE uses filter instead)
                // Use a regex to work around a WebKit issue. See #5145
-               opacity: /^0.55/.test( a.style.opacity ),
+               opacity: /^0.5/.test( a.style.opacity ),
 
                // Verify style float existence
                // (IE uses styleFloat instead of cssFloat)
@@ -1439,6 +1317,9 @@ jQuery.support = (function() {
                // Where outerHTML is undefined, this still works
                html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
 
+               // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+               boxModel: ( document.compatMode === "CSS1Compat" ),
+
                // Will be defined later
                submitBubbles: true,
                changeBubbles: true,
@@ -1448,12 +1329,10 @@ jQuery.support = (function() {
                inlineBlockNeedsLayout: false,
                shrinkWrapBlocks: false,
                reliableMarginRight: true,
-               pixelMargin: true
+               boxSizingReliable: true,
+               pixelPosition: false
        };
 
-       // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead
-       jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat");
-
        // Make sure checked status is properly cloned
        input.checked = true;
        support.noCloneChecked = input.cloneNode( true ).checked;
@@ -1472,22 +1351,23 @@ jQuery.support = (function() {
        }
 
        if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
-               div.attachEvent( "onclick", function() {
+               div.attachEvent( "onclick", clickFn = function() {
                        // Cloning a node shouldn't copy over any
                        // bound event handlers (IE does this)
                        support.noCloneEvent = false;
                });
-               div.cloneNode( true ).fireEvent( "onclick" );
+               div.cloneNode( true ).fireEvent("onclick");
+               div.detachEvent( "onclick", clickFn );
        }
 
        // Check if a radio maintains its value
        // after being appended to the DOM
        input = document.createElement("input");
        input.value = "t";
-       input.setAttribute("type", "radio");
+       input.setAttribute( "type", "radio" );
        support.radioValue = input.value === "t";
 
-       input.setAttribute("checked", "checked");
+       input.setAttribute( "checked", "checked" );
 
        // #11217 - WebKit loses check when the name is after the checked attribute
        input.setAttribute( "name", "t" );
@@ -1514,9 +1394,9 @@ jQuery.support = (function() {
        // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
        if ( div.attachEvent ) {
                for ( i in {
-                       submit: 1,
-                       change: 1,
-                       focusin: 1
+                       submit: true,
+                       change: true,
+                       focusin: true
                }) {
                        eventName = "on" + i;
                        isSupported = ( eventName in div );
@@ -1528,16 +1408,10 @@ jQuery.support = (function() {
                }
        }
 
-       fragment.removeChild( div );
-
-       // Null elements to avoid leaks in IE
-       fragment = select = opt = div = input = null;
-
        // Run tests that need a body at doc ready
        jQuery(function() {
-               var container, outer, inner, table, td, offsetSupport,
-                       marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight,
-                       paddingMarginBorderVisibility, paddingMarginBorder,
+               var container, div, tds, marginDiv,
+                       divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
                        body = document.getElementsByTagName("body")[0];
 
                if ( !body ) {
@@ -1545,17 +1419,8 @@ jQuery.support = (function() {
                        return;
                }
 
-               conMarginTop = 1;
-               paddingMarginBorder = "padding:0;margin:0;border:";
-               positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;";
-               paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;";
-               style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;";
-               html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" +
-                       "<table " + style + "' cellpadding='0' cellspacing='0'>" +
-                       "<tr><td></td></tr></table>";
-
                container = document.createElement("div");
-               container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+               container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
                body.insertBefore( container, body.firstChild );
 
                // Construct the test element
@@ -1569,8 +1434,9 @@ jQuery.support = (function() {
                // display:none (it is still safe to use offsets if a parent element is
                // hidden; don safety goggles and see bug #4512 for more information).
                // (only IE 8 fails this test)
-               div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>";
-               tds = div.getElementsByTagName( "td" );
+               div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
+               tds = div.getElementsByTagName("td");
+               tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
                isSupported = ( tds[ 0 ].offsetHeight === 0 );
 
                tds[ 0 ].style.display = "";
@@ -1580,20 +1446,32 @@ jQuery.support = (function() {
                // (IE <= 8 fail this test)
                support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
 
-               // Check if div with explicit width and no margin-right incorrectly
-               // gets computed margin-right based on width of container. For more
-               // info see bug #3333
-               // Fails in WebKit before Feb 2011 nightlies
-               // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+               // Check box-sizing and margin behavior
+               div.innerHTML = "";
+               div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+               support.boxSizing = ( div.offsetWidth === 4 );
+               support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+               // NOTE: To any future maintainer, window.getComputedStyle was used here
+               // instead of getComputedStyle because it gave a better gzip size.
+               // The difference between window.getComputedStyle and getComputedStyle is
+               // 7 bytes
                if ( window.getComputedStyle ) {
-                       div.innerHTML = "";
-                       marginDiv = document.createElement( "div" );
-                       marginDiv.style.width = "0";
-                       marginDiv.style.marginRight = "0";
-                       div.style.width = "2px";
+                       support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+                       support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+                       // Check if div with explicit width and no margin-right incorrectly
+                       // gets computed margin-right based on width of container. For more
+                       // info see bug #3333
+                       // Fails in WebKit before Feb 2011 nightlies
+                       // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+                       marginDiv = document.createElement("div");
+                       marginDiv.style.cssText = div.style.cssText = divReset;
+                       marginDiv.style.marginRight = marginDiv.style.width = "0";
+                       div.style.width = "1px";
                        div.appendChild( marginDiv );
                        support.reliableMarginRight =
-                               ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+                               !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
                }
 
                if ( typeof div.style.zoom !== "undefined" ) {
@@ -1602,73 +1480,39 @@ jQuery.support = (function() {
                        // them layout
                        // (IE < 8 does this)
                        div.innerHTML = "";
-                       div.style.width = div.style.padding = "1px";
-                       div.style.border = 0;
-                       div.style.overflow = "hidden";
-                       div.style.display = "inline";
-                       div.style.zoom = 1;
+                       div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
                        support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
 
                        // Check if elements with layout shrink-wrap their children
                        // (IE 6 does this)
                        div.style.display = "block";
                        div.style.overflow = "visible";
-                       div.innerHTML = "<div style='width:5px;'></div>";
+                       div.innerHTML = "<div></div>";
+                       div.firstChild.style.width = "5px";
                        support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
-               }
-
-               div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility;
-               div.innerHTML = html;
-
-               outer = div.firstChild;
-               inner = outer.firstChild;
-               td = outer.nextSibling.firstChild.firstChild;
-
-               offsetSupport = {
-                       doesNotAddBorder: ( inner.offsetTop !== 5 ),
-                       doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
-               };
 
-               inner.style.position = "fixed";
-               inner.style.top = "20px";
-
-               // safari subtracts parent border width here which is 5px
-               offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
-               inner.style.position = inner.style.top = "";
-
-               outer.style.overflow = "hidden";
-               outer.style.position = "relative";
-
-               offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
-               offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
-
-               if ( window.getComputedStyle ) {
-                       div.style.marginTop = "1%";
-                       support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%";
-               }
-
-               if ( typeof container.style.zoom !== "undefined" ) {
                        container.style.zoom = 1;
                }
 
+               // Null elements to avoid leaks in IE
                body.removeChild( container );
-               marginDiv = div = container = null;
-
-               jQuery.extend( support, offsetSupport );
+               container = div = tds = marginDiv = null;
        });
 
+       // Null elements to avoid leaks in IE
+       fragment.removeChild( div );
+       all = a = select = opt = input = fragment = div = null;
+
        return support;
 })();
-
-
-
-
 var rbrace = /^(?:\{.*\}|\[.*\])$/,
        rmultiDash = /([A-Z])/g;
 
 jQuery.extend({
        cache: {},
 
+       deletedIds: [],
+
        // Please use with caution
        uuid: 0,
 
@@ -1695,7 +1539,7 @@ jQuery.extend({
                        return;
                }
 
-               var privateCache, thisCache, ret,
+               var thisCache, ret,
                        internalKey = jQuery.expando,
                        getByName = typeof name === "string",
 
@@ -1709,12 +1553,11 @@ jQuery.extend({
 
                        // Only defining an ID for JS objects if its cache already exists allows
                        // the code to shortcut on the same path as a DOM node with no cache
-                       id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
-                       isEvents = name === "events";
+                       id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
 
                // Avoid doing any more work than we need to when trying to get data on an
                // object that has no data at all
-               if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+               if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
                        return;
                }
 
@@ -1722,7 +1565,7 @@ jQuery.extend({
                        // Only DOM nodes need a new unique ID for each element since their data
                        // ends up in the global cache
                        if ( isNode ) {
-                               elem[ internalKey ] = id = ++jQuery.uuid;
+                               elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid;
                        } else {
                                id = internalKey;
                        }
@@ -1748,7 +1591,7 @@ jQuery.extend({
                        }
                }
 
-               privateCache = thisCache = cache[ id ];
+               thisCache = cache[ id ];
 
                // jQuery data() is stored in a separate object inside the object's internal data
                // cache in order to avoid key collisions between internal data and user-defined
@@ -1765,12 +1608,6 @@ jQuery.extend({
                        thisCache[ jQuery.camelCase( name ) ] = data;
                }
 
-               // Users should not attempt to inspect the internal events object using jQuery.data,
-               // it is undocumented and subject to change. But does anyone listen? No.
-               if ( isEvents && !thisCache[ name ] ) {
-                       return privateCache.events;
-               }
-
                // Check for both converted-to-camel and non-converted data property names
                // If a data property was specified
                if ( getByName ) {
@@ -1798,16 +1635,11 @@ jQuery.extend({
 
                var thisCache, i, l,
 
-                       // Reference to internal data cache key
-                       internalKey = jQuery.expando,
-
                        isNode = elem.nodeType,
 
                        // See jQuery.data for more information
                        cache = isNode ? jQuery.cache : elem,
-
-                       // See jQuery.data for more information
-                       id = isNode ? elem[ internalKey ] : internalKey;
+                       id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
 
                // If there is already no cache entry for this object, there is no
                // purpose in continuing
@@ -1834,7 +1666,7 @@ jQuery.extend({
                                                if ( name in thisCache ) {
                                                        name = [ name ];
                                                } else {
-                                                       name = name.split( " " );
+                                                       name = name.split(" ");
                                                }
                                        }
                                }
@@ -1857,35 +1689,23 @@ jQuery.extend({
 
                        // Don't destroy the parent cache unless the internal data object
                        // had been the only thing left in it
-                       if ( !isEmptyDataObject(cache[ id ]) ) {
+                       if ( !isEmptyDataObject( cache[ id ] ) ) {
                                return;
                        }
                }
 
-               // Browsers that fail expando deletion also refuse to delete expandos on
-               // the window, but it will allow it on all other JS objects; other browsers
-               // don't care
-               // Ensure that `cache` is not a window object #10080
-               if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+               // Destroy the cache
+               if ( isNode ) {
+                       jQuery.cleanData( [ elem ], true );
+
+               // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+               } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
                        delete cache[ id ];
+
+               // When all else fails, null
                } else {
                        cache[ id ] = null;
                }
-
-               // We destroyed the cache and need to eliminate the expando on the node to avoid
-               // false lookups in the cache for entries that no longer exist
-               if ( isNode ) {
-                       // IE does not allow us to delete expando properties from nodes,
-                       // nor does it have a removeAttribute function on Document nodes;
-                       // we must handle all of these cases
-                       if ( jQuery.support.deleteExpando ) {
-                               delete elem[ internalKey ];
-                       } else if ( elem.removeAttribute ) {
-                               elem.removeAttribute( internalKey );
-                       } else {
-                               elem[ internalKey ] = null;
-                       }
-               }
        },
 
        // For internal use only.
@@ -1895,15 +1715,10 @@ jQuery.extend({
 
        // A method for determining if a DOM node can handle the data expando
        acceptData: function( elem ) {
-               if ( elem.nodeName ) {
-                       var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
-
-                       if ( match ) {
-                               return !(match === true || elem.getAttribute("classid") !== match);
-                       }
-               }
+               var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
 
-               return true;
+               // nodes accept data unless otherwise specified; rejection can be conditional
+               return !noData || noData !== true && elem.getAttribute("classid") === noData;
        }
 });
 
@@ -1996,8 +1811,9 @@ function dataAttr( elem, key, data ) {
                                data = data === "true" ? true :
                                data === "false" ? false :
                                data === "null" ? null :
-                               jQuery.isNumeric( data ) ? +data :
-                                       rbrace.test( data ) ? jQuery.parseJSON( data ) :
+                               // Only convert to a number if it doesn't change the string
+                               +data + "" === data ? +data :
+                               rbrace.test( data ) ? jQuery.parseJSON( data ) :
                                        data;
                        } catch( e ) {}
 
@@ -2014,7 +1830,8 @@ function dataAttr( elem, key, data ) {
 
 // checks a cache object for emptiness
 function isEmptyDataObject( obj ) {
-       for ( var name in obj ) {
+       var name;
+       for ( name in obj ) {
 
                // if the public data object is empty, the private is still empty
                if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
@@ -2027,73 +1844,23 @@ function isEmptyDataObject( obj ) {
 
        return true;
 }
-
-
-
-
-function handleQueueMarkDefer( elem, type, src ) {
-       var deferDataKey = type + "defer",
-               queueDataKey = type + "queue",
-               markDataKey = type + "mark",
-               defer = jQuery._data( elem, deferDataKey );
-       if ( defer &&
-               ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
-               ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
-               // Give room for hard-coded callbacks to fire first
-               // and eventually mark/queue something else on the element
-               setTimeout( function() {
-                       if ( !jQuery._data( elem, queueDataKey ) &&
-                               !jQuery._data( elem, markDataKey ) ) {
-                               jQuery.removeData( elem, deferDataKey, true );
-                               defer.fire();
-                       }
-               }, 0 );
-       }
-}
-
 jQuery.extend({
-
-       _mark: function( elem, type ) {
-               if ( elem ) {
-                       type = ( type || "fx" ) + "mark";
-                       jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
-               }
-       },
-
-       _unmark: function( force, elem, type ) {
-               if ( force !== true ) {
-                       type = elem;
-                       elem = force;
-                       force = false;
-               }
-               if ( elem ) {
-                       type = type || "fx";
-                       var key = type + "mark",
-                               count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
-                       if ( count ) {
-                               jQuery._data( elem, key, count );
-                       } else {
-                               jQuery.removeData( elem, key, true );
-                               handleQueueMarkDefer( elem, type, "mark" );
-                       }
-               }
-       },
-
        queue: function( elem, type, data ) {
-               var q;
+               var queue;
+
                if ( elem ) {
                        type = ( type || "fx" ) + "queue";
-                       q = jQuery._data( elem, type );
+                       queue = jQuery._data( elem, type );
 
                        // Speed up dequeue by getting out quickly if this is just a lookup
                        if ( data ) {
-                               if ( !q || jQuery.isArray(data) ) {
-                                       q = jQuery._data( elem, type, jQuery.makeArray(data) );
+                               if ( !queue || jQuery.isArray(data) ) {
+                                       queue = jQuery._data( elem, type, jQuery.makeArray(data) );
                                } else {
-                                       q.push( data );
+                                       queue.push( data );
                                }
                        }
-                       return q || [];
+                       return queue || [];
                }
        },
 
@@ -2102,7 +1869,10 @@ jQuery.extend({
 
                var queue = jQuery.queue( elem, type ),
                        fn = queue.shift(),
-                       hooks = {};
+                       hooks = jQuery._queueHooks( elem, type ),
+                       next = function() {
+                               jQuery.dequeue( elem, type );
+                       };
 
                // If the fx queue is dequeued, always remove the progress sentinel
                if ( fn === "inprogress" ) {
@@ -2110,22 +1880,31 @@ jQuery.extend({
                }
 
                if ( fn ) {
+
                        // Add a progress sentinel to prevent the fx queue from being
                        // automatically dequeued
                        if ( type === "fx" ) {
                                queue.unshift( "inprogress" );
                        }
 
-                       jQuery._data( elem, type + ".run", hooks );
-                       fn.call( elem, function() {
-                               jQuery.dequeue( elem, type );
-                       }, hooks );
+                       // clear up the last queue stop function
+                       delete hooks.stop;
+                       fn.call( elem, next, hooks );
                }
-
-               if ( !queue.length ) {
-                       jQuery.removeData( elem, type + "queue " + type + ".run", true );
-                       handleQueueMarkDefer( elem, type, "queue" );
+               if ( !queue.length && hooks ) {
+                       hooks.empty.fire();
                }
+       },
+
+       // not intended for public consumption - generates a queueHooks object, or returns the current one
+       _queueHooks: function( elem, type ) {
+               var key = type + "queueHooks";
+               return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+                       empty: jQuery.Callbacks("once memory").add(function() {
+                               jQuery.removeData( elem, type + "queue", true );
+                               jQuery.removeData( elem, key, true );
+                       })
+               });
        }
 });
 
@@ -2148,6 +1927,9 @@ jQuery.fn.extend({
                        this.each(function() {
                                var queue = jQuery.queue( this, type, data );
 
+                               // ensure a hooks for this queue
+                               jQuery._queueHooks( this, type );
+
                                if ( type === "fx" && queue[0] !== "inprogress" ) {
                                        jQuery.dequeue( this, type );
                                }
@@ -2176,51 +1958,42 @@ jQuery.fn.extend({
        },
        // Get a promise resolved when queues of a certain type
        // are emptied (fx is the type by default)
-       promise: function( type, object ) {
+       promise: function( type, obj ) {
+               var tmp,
+                       count = 1,
+                       defer = jQuery.Deferred(),
+                       elements = this,
+                       i = this.length,
+                       resolve = function() {
+                               if ( !( --count ) ) {
+                                       defer.resolveWith( elements, [ elements ] );
+                               }
+                       };
+
                if ( typeof type !== "string" ) {
-                       object = type;
+                       obj = type;
                        type = undefined;
                }
                type = type || "fx";
-               var defer = jQuery.Deferred(),
-                       elements = this,
-                       i = elements.length,
-                       count = 1,
-                       deferDataKey = type + "defer",
-                       queueDataKey = type + "queue",
-                       markDataKey = type + "mark",
-                       tmp;
-               function resolve() {
-                       if ( !( --count ) ) {
-                               defer.resolveWith( elements, [ elements ] );
-                       }
-               }
+
                while( i-- ) {
-                       if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
-                                       ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
-                                               jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
-                                       jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+                       if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) {
                                count++;
-                               tmp.add( resolve );
+                               tmp.empty.add( resolve );
                        }
                }
                resolve();
-               return defer.promise( object );
+               return defer.promise( obj );
        }
 });
-
-
-
-
-var rclass = /[\n\t\r]/g,
-       rspace = /\s+/,
+var nodeHook, boolHook, fixSpecified,
+       rclass = /[\t\r\n]/g,
        rreturn = /\r/g,
        rtype = /^(?:button|input)$/i,
        rfocusable = /^(?:button|input|object|select|textarea)$/i,
-       rclickable = /^a(?:rea)?$/i,
+       rclickable = /^a(?:rea|)$/i,
        rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
-       getSetAttribute = jQuery.support.getSetAttribute,
-       nodeHook, boolHook, fixSpecified;
+       getSetAttribute = jQuery.support.getSetAttribute;
 
 jQuery.fn.extend({
        attr: function( name, value ) {
@@ -2259,7 +2032,7 @@ jQuery.fn.extend({
                }
 
                if ( value && typeof value === "string" ) {
-                       classNames = value.split( rspace );
+                       classNames = value.split( core_rspace );
 
                        for ( i = 0, l = this.length; i < l; i++ ) {
                                elem = this[ i ];
@@ -2286,31 +2059,30 @@ jQuery.fn.extend({
        },
 
        removeClass: function( value ) {
-               var classNames, i, l, elem, className, c, cl;
+               var removes, className, elem, c, cl, i, l;
 
                if ( jQuery.isFunction( value ) ) {
                        return this.each(function( j ) {
                                jQuery( this ).removeClass( value.call(this, j, this.className) );
                        });
                }
-
                if ( (value && typeof value === "string") || value === undefined ) {
-                       classNames = ( value || "" ).split( rspace );
+                       removes = ( value || "" ).split( core_rspace );
 
                        for ( i = 0, l = this.length; i < l; i++ ) {
                                elem = this[ i ];
-
                                if ( elem.nodeType === 1 && elem.className ) {
-                                       if ( value ) {
-                                               className = (" " + elem.className + " ").replace( rclass, " " );
-                                               for ( c = 0, cl = classNames.length; c < cl; c++ ) {
-                                                       className = className.replace(" " + classNames[ c ] + " ", " ");
-                                               }
-                                               elem.className = jQuery.trim( className );
 
-                                       } else {
-                                               elem.className = "";
+                                       className = (" " + elem.className + " ").replace( rclass, " " );
+
+                                       // loop over each item in the removal list
+                                       for ( c = 0, cl = removes.length; c < cl; c++ ) {
+                                               // Remove until there is nothing to remove,
+                                               while ( className.indexOf(" " + removes[ c ] + " ") > -1 ) {
+                                                       className = className.replace( " " + removes[ c ] + " " , " " );
+                                               }
                                        }
+                                       elem.className = value ? jQuery.trim( className ) : "";
                                }
                        }
                }
@@ -2335,10 +2107,10 @@ jQuery.fn.extend({
                                        i = 0,
                                        self = jQuery( this ),
                                        state = stateVal,
-                                       classNames = value.split( rspace );
+                                       classNames = value.split( core_rspace );
 
                                while ( (className = classNames[ i++ ]) ) {
-                                       // check each className given, space seperated list
+                                       // check each className given, space separated list
                                        state = isBool ? state : !self.hasClass( className );
                                        self[ state ? "addClass" : "removeClass" ]( className );
                                }
@@ -2395,7 +2167,8 @@ jQuery.fn.extend({
                isFunction = jQuery.isFunction( value );
 
                return this.each(function( i ) {
-                       var self = jQuery(this), val;
+                       var val,
+                               self = jQuery(this);
 
                        if ( this.nodeType !== 1 ) {
                                return;
@@ -2497,16 +2270,8 @@ jQuery.extend({
                }
        },
 
-       attrFn: {
-               val: true,
-               css: true,
-               html: true,
-               text: true,
-               data: true,
-               width: true,
-               height: true,
-               offset: true
-       },
+       // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+       attrFn: {},
 
        attr: function( elem, name, value, pass ) {
                var ret, hooks, notxml,
@@ -2517,7 +2282,7 @@ jQuery.extend({
                        return;
                }
 
-               if ( pass && name in jQuery.attrFn ) {
+               if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
                        return jQuery( elem )[ name ]( value );
                }
 
@@ -2564,14 +2329,14 @@ jQuery.extend({
        },
 
        removeAttr: function( elem, value ) {
-               var propName, attrNames, name, l, isBool,
+               var propName, attrNames, name, isBool,
                        i = 0;
 
                if ( value && elem.nodeType === 1 ) {
-                       attrNames = value.toLowerCase().split( rspace );
-                       l = attrNames.length;
 
-                       for ( ; i < l; i++ ) {
+                       attrNames = value.split( core_rspace );
+
+                       for ( ; i < attrNames.length; i++ ) {
                                name = attrNames[ i ];
 
                                if ( name ) {
@@ -2701,9 +2466,6 @@ jQuery.extend({
        }
 });
 
-// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
-jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
-
 // Hook for boolean attributes
 boolHook = {
        get: function( elem, name ) {
@@ -2750,8 +2512,8 @@ if ( !getSetAttribute ) {
                get: function( elem, name ) {
                        var ret;
                        ret = elem.getAttributeNode( name );
-                       return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
-                               ret.nodeValue :
+                       return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+                               ret.value :
                                undefined;
                },
                set: function( elem, value, name ) {
@@ -2761,13 +2523,10 @@ if ( !getSetAttribute ) {
                                ret = document.createAttribute( name );
                                elem.setAttributeNode( ret );
                        }
-                       return ( ret.nodeValue = value + "" );
+                       return ( ret.value = value + "" );
                }
        };
 
-       // Apply the nodeHook to tabindex
-       jQuery.attrHooks.tabindex.set = nodeHook.set;
-
        // Set width and height to auto instead of 0 on empty string( Bug #8150 )
        // This is for removals
        jQuery.each([ "width", "height" ], function( i, name ) {
@@ -2865,35 +2624,12 @@ jQuery.each([ "radio", "checkbox" ], function() {
                }
        });
 });
-
-
-
-
 var rformElems = /^(?:textarea|input|select)$/i,
-       rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
-       rhoverHack = /(?:^|\s)hover(\.\S+)?\b/,
+       rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+       rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
        rkeyEvent = /^key/,
        rmouseEvent = /^(?:mouse|contextmenu)|click/,
        rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
-       rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
-       quickParse = function( selector ) {
-               var quick = rquickIs.exec( selector );
-               if ( quick ) {
-                       //   0  1    2   3
-                       // [ _, tag, id, class ]
-                       quick[1] = ( quick[1] || "" ).toLowerCase();
-                       quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
-               }
-               return quick;
-       },
-       quickIs = function( elem, m ) {
-               var attrs = elem.attributes || {};
-               return (
-                       (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
-                       (!m[2] || (attrs.id || {}).value === m[2]) &&
-                       (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
-               );
-       },
        hoverHack = function( events ) {
                return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
        };
@@ -2908,7 +2644,7 @@ jQuery.event = {
 
                var elemData, eventHandle, events,
                        t, tns, type, namespaces, handleObj,
-                       handleObjIn, quick, handlers, special;
+                       handleObjIn, handlers, special;
 
                // Don't attach events to noData or text/comment nodes (allow plain objects tho)
                if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
@@ -2971,7 +2707,6 @@ jQuery.event = {
                                handler: handler,
                                guid: handler.guid,
                                selector: selector,
-                               quick: selector && quickParse( selector ),
                                namespace: namespaces.join(".")
                        }, handleObjIn );
 
@@ -3021,9 +2756,9 @@ jQuery.event = {
        // Detach an event or set of events from an element
        remove: function( elem, types, handler, selector, mappedTypes ) {
 
-               var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
-                       t, tns, type, origType, namespaces, origCount,
-                       j, events, special, handle, eventType, handleObj;
+               var t, tns, type, origType, namespaces, origCount,
+                       j, events, special, eventType, handleObj,
+                       elemData = jQuery.hasData( elem ) && jQuery._data( elem );
 
                if ( !elemData || !(events = elemData.events) ) {
                        return;
@@ -3048,7 +2783,7 @@ jQuery.event = {
                        type = ( selector? special.delegateType : special.bindType ) || type;
                        eventType = events[ type ] || [];
                        origCount = eventType.length;
-                       namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+                       namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
 
                        // Remove matching events
                        for ( j = 0; j < eventType.length; j++ ) {
@@ -3072,7 +2807,7 @@ jQuery.event = {
                        // Remove generic event handler if we removed something and no more handlers exist
                        // (avoids potential for endless recursion during removal of special event handlers)
                        if ( eventType.length === 0 && origCount !== eventType.length ) {
-                               if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+                               if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
                                        jQuery.removeEvent( elem, type, elemData.handle );
                                }
 
@@ -3082,14 +2817,11 @@ jQuery.event = {
 
                // Remove the expando if it's no longer used
                if ( jQuery.isEmptyObject( events ) ) {
-                       handle = elemData.handle;
-                       if ( handle ) {
-                               handle.elem = null;
-                       }
+                       delete elemData.handle;
 
                        // removeData also checks for emptiness and clears the expando if empty
                        // so use it instead of delete
-                       jQuery.removeData( elem, [ "events", "handle" ], true );
+                       jQuery.removeData( elem, "events", true );
                }
        },
 
@@ -3108,9 +2840,9 @@ jQuery.event = {
                }
 
                // Event object or event type
-               var type = event.type || event,
-                       namespaces = [],
-                       cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+               var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+                       type = event.type || event,
+                       namespaces = [];
 
                // focus/blur morphs to focusin/out; ensure we're not firing them right now
                if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
@@ -3148,7 +2880,7 @@ jQuery.event = {
                event.isTrigger = true;
                event.exclusive = exclusive;
                event.namespace = namespaces.join( "." );
-               event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+               event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
                ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
 
                // Handle a global trigger
@@ -3187,14 +2919,13 @@ jQuery.event = {
 
                        bubbleType = special.delegateType || type;
                        cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
-                       old = null;
-                       for ( ; cur; cur = cur.parentNode ) {
+                       for ( old = elem; cur; cur = cur.parentNode ) {
                                eventPath.push([ cur, bubbleType ]);
                                old = cur;
                        }
 
                        // Only add window if we got to document (e.g., not plain obj or detached DOM)
-                       if ( old && old === elem.ownerDocument ) {
+                       if ( old === (elem.ownerDocument || document) ) {
                                eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
                        }
                }
@@ -3256,13 +2987,13 @@ jQuery.event = {
                // Make a writable jQuery.Event from the native event object
                event = jQuery.event.fix( event || window.event );
 
-               var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+               var i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related,
+                       handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
                        delegateCount = handlers.delegateCount,
-                       args = [].slice.call( arguments, 0 ),
+                       args = [].slice.call( arguments ),
                        run_all = !event.exclusive && !event.namespace,
                        special = jQuery.event.special[ event.type ] || {},
-                       handlerQueue = [],
-                       i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+                       handlerQueue = [];
 
                // Use the fix-ed jQuery.Event rather than the (read-only) native event
                args[0] = event;
@@ -3279,12 +3010,12 @@ jQuery.event = {
 
                        // Pregenerate a single jQuery object for reuse with .is()
                        jqcur = jQuery(this);
-                       jqcur.context = this.ownerDocument || this;
+                       jqcur.context = this;
 
                        for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
 
-                               // Don't process events on disabled elements (#6911, #8165)
-                               if ( cur.disabled !== true ) {
+                               // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #xxxx)
+                               if ( cur.disabled !== true || event.type !== "click" ) {
                                        selMatch = {};
                                        matches = [];
                                        jqcur[0] = cur;
@@ -3293,9 +3024,7 @@ jQuery.event = {
                                                sel = handleObj.selector;
 
                                                if ( selMatch[ sel ] === undefined ) {
-                                                       selMatch[ sel ] = (
-                                                               handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
-                                                       );
+                                                       selMatch[ sel ] = jqcur.is( sel );
                                                }
                                                if ( selMatch[ sel ] ) {
                                                        matches.push( handleObj );
@@ -3429,10 +3158,8 @@ jQuery.event = {
                        event.target = event.target.parentNode;
                }
 
-               // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
-               if ( event.metaKey === undefined ) {
-                       event.metaKey = event.ctrlKey;
-               }
+               // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
+               event.metaKey = !!event.metaKey;
 
                return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
        },
@@ -3505,8 +3232,17 @@ jQuery.removeEvent = document.removeEventListener ?
                }
        } :
        function( elem, type, handle ) {
+               var name = "on" + type;
+
                if ( elem.detachEvent ) {
-                       elem.detachEvent( "on" + type, handle );
+
+                       // #8545, #7054, preventing memory leaks for custom events in IE6-8 –
+                       // detachEvent needed property on element, by name of that event, to properly expose it to GC
+                       if ( typeof elem[ name ] === "undefined" ) {
+                               elem[ name ] = null;
+                       }
+
+                       elem.detachEvent( name, handle );
                }
        };
 
@@ -3603,11 +3339,11 @@ jQuery.each({
                bindType: fix,
 
                handle: function( event ) {
-                       var target = this,
+                       var ret,
+                               target = this,
                                related = event.relatedTarget,
                                handleObj = event.handleObj,
-                               selector = handleObj.selector,
-                               ret;
+                               selector = handleObj.selector;
 
                        // For mousenter/leave call the handler if related is outside the target.
                        // NB: No relatedTarget if the mouse left/entered the browser window
@@ -3636,16 +3372,16 @@ if ( !jQuery.support.submitBubbles ) {
                                // Node name check avoids a VML-related crash in IE (#9807)
                                var elem = e.target,
                                        form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
-                               if ( form && !form._submit_attached ) {
+                               if ( form && !jQuery._data( form, "_submit_attached" ) ) {
                                        jQuery.event.add( form, "submit._submit", function( event ) {
                                                event._submit_bubble = true;
                                        });
-                                       form._submit_attached = true;
+                                       jQuery._data( form, "_submit_attached", true );
                                }
                        });
                        // return undefined since we don't need an event listener
                },
-               
+
                postDispatch: function( event ) {
                        // If form was submitted by the user, bubble the event up the tree
                        if ( event._submit_bubble ) {
@@ -3688,8 +3424,9 @@ if ( !jQuery.support.changeBubbles ) {
                                        jQuery.event.add( this, "click._change", function( event ) {
                                                if ( this._just_changed && !event.isTrigger ) {
                                                        this._just_changed = false;
-                                                       jQuery.event.simulate( "change", this, event, true );
                                                }
+                                               // Allow triggered, simulated change events (#11500)
+                                               jQuery.event.simulate( "change", this, event, true );
                                        });
                                }
                                return false;
@@ -3698,13 +3435,13 @@ if ( !jQuery.support.changeBubbles ) {
                        jQuery.event.add( this, "beforeactivate._change", function( e ) {
                                var elem = e.target;
 
-                               if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+                               if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
                                        jQuery.event.add( elem, "change._change", function( event ) {
                                                if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
                                                        jQuery.event.simulate( "change", this.parentNode, event, true );
                                                }
                                        });
-                                       elem._change_attached = true;
+                                       jQuery._data( elem, "_change_attached", true );
                                }
                        });
                },
@@ -3810,9 +3547,10 @@ jQuery.fn.extend({
                return this.on( types, selector, data, fn, 1 );
        },
        off: function( types, selector, fn ) {
+               var handleObj, type;
                if ( types && types.preventDefault && types.handleObj ) {
                        // ( event )  dispatched jQuery.Event
-                       var handleObj = types.handleObj;
+                       handleObj = types.handleObj;
                        jQuery( types.delegateTarget ).off(
                                handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
                                handleObj.selector,
@@ -3822,7 +3560,7 @@ jQuery.fn.extend({
                }
                if ( typeof types === "object" ) {
                        // ( types-object [, selector] )
-                       for ( var type in types ) {
+                       for ( type in types ) {
                                this.off( type, selector, types[ type ] );
                        }
                        return this;
@@ -3861,7 +3599,7 @@ jQuery.fn.extend({
        },
        undelegate: function( selector, types, fn ) {
                // ( namespace ) or ( selector, types [, fn] )
-               return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+               return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
        },
 
        trigger: function( type, data ) {
@@ -3922,10 +3660,6 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl
                        this.trigger( name );
        };
 
-       if ( jQuery.attrFn ) {
-               jQuery.attrFn[ name ] = true;
-       }
-
        if ( rkeyEvent.test( name ) ) {
                jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
        }
@@ -3934,1609 +3668,1595 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl
                jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
        }
 });
+/*!\r
+ * Sizzle CSS Selector Engine\r
+ *  Copyright 2012 jQuery Foundation and other contributors\r
+ *  Released under the MIT license\r
+ *  http://sizzlejs.com/\r
+ */\r
+(function( window, undefined ) {\r
+\r
+var cachedruns,\r
+       dirruns,\r
+       sortOrder,\r
+       siblingCheck,\r
+       assertGetIdNotName,\r
+\r
+       document = window.document,\r
+       docElem = document.documentElement,\r
+\r
+       strundefined = "undefined",\r
+       hasDuplicate = false,\r
+       baseHasDuplicate = true,\r
+       done = 0,\r
+       slice = [].slice,\r
+       push = [].push,\r
+\r
+       expando = ( "sizcache" + Math.random() ).replace( ".", "" ),\r
+\r
+       // Regex\r
+\r
+       // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\r
+       whitespace = "[\\x20\\t\\r\\n\\f]",\r
+       // http://www.w3.org/TR/css3-syntax/#characters\r
+       characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",\r
+\r
+       // Loosely modeled on CSS identifier characters\r
+       // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)\r
+       // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\r
+       identifier = characterEncoding.replace( "w", "w#" ),\r
+\r
+       // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\r
+       operators = "([*^$|!~]?=)",\r
+       attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +\r
+               "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",\r
+       pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)",\r
+       pos = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",\r
+       combinators = whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*",\r
+       groups = "(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|" + attributes + "|" + pseudos.replace( 2, 7 ) + "|[^\\\\(),])+",\r
+\r
+       // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\r
+       rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),\r
+\r
+       rcombinators = new RegExp( "^" + combinators ),\r
+\r
+       // All simple (non-comma) selectors, excluding insignifant trailing whitespace\r
+       rgroups = new RegExp( groups + "?(?=" + whitespace + "*,|$)", "g" ),\r
+\r
+       // A selector, or everything after leading whitespace\r
+       // Optionally followed in either case by a ")" for terminating sub-selectors\r
+       rselector = new RegExp( "^(?:(?!,)(?:(?:^|,)" + whitespace + "*" + groups + ")*?|" + whitespace + "*(.*?))(\\)|$)" ),\r
+\r
+       // All combinators and selector components (attribute test, tag, pseudo, etc.), the latter appearing together when consecutive\r
+       rtokens = new RegExp( groups.slice( 19, -6 ) + "\\x20\\t\\r\\n\\f>+~])+|" + combinators, "g" ),\r
+\r
+       // Easily-parseable/retrievable ID or TAG or CLASS selectors\r
+       rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,\r
+\r
+       rsibling = /[\x20\t\r\n\f]*[+~]/,\r
+       rendsWithNot = /:not\($/,\r
+\r
+       rheader = /h\d/i,\r
+       rinputs = /input|select|textarea|button/i,\r
+\r
+       rbackslash = /\\(?!\\)/g,\r
+\r
+       matchExpr = {\r
+               "ID": new RegExp( "^#(" + characterEncoding + ")" ),\r
+               "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),\r
+               "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),\r
+               "TAG": new RegExp( "^(" + characterEncoding.replace( "[-", "[-\\*" ) + ")" ),\r
+               "ATTR": new RegExp( "^" + attributes ),\r
+               "PSEUDO": new RegExp( "^" + pseudos ),\r
+               "CHILD": new RegExp( "^:(only|nth|last|first)-child(?:\\(" + whitespace +\r
+                       "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +\r
+                       "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),\r
+               "POS": new RegExp( pos, "ig" ),\r
+               // For use in libraries implementing .is()\r
+               "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )\r
+       },\r
+\r
+       classCache = {},\r
+       cachedClasses = [],\r
+       compilerCache = {},\r
+       cachedSelectors = [],\r
+\r
+       // Mark a function for use in filtering\r
+       markFunction = function( fn ) {\r
+               fn.sizzleFilter = true;\r
+               return fn;\r
+       },\r
+\r
+       // Returns a function to use in pseudos for input types\r
+       createInputFunction = function( type ) {\r
+               return function( elem ) {\r
+                       // Check the input's nodeName and type\r
+                       return elem.nodeName.toLowerCase() === "input" && elem.type === type;\r
+               };\r
+       },\r
+\r
+       // Returns a function to use in pseudos for buttons\r
+       createButtonFunction = function( type ) {\r
+               return function( elem ) {\r
+                       var name = elem.nodeName.toLowerCase();\r
+                       return (name === "input" || name === "button") && elem.type === type;\r
+               };\r
+       },\r
+\r
+       // Used for testing something on an element\r
+       assert = function( fn ) {\r
+               var pass = false,\r
+                       div = document.createElement("div");\r
+               try {\r
+                       pass = fn( div );\r
+               } catch (e) {}\r
+               // release memory in IE\r
+               div = null;\r
+               return pass;\r
+       },\r
+\r
+       // Check if attributes should be retrieved by attribute nodes\r
+       assertAttributes = assert(function( div ) {\r
+               div.innerHTML = "<select></select>";\r
+               var type = typeof div.lastChild.getAttribute("multiple");\r
+               // IE8 returns a string for some attributes even when not present\r
+               return type !== "boolean" && type !== "string";\r
+       }),\r
+\r
+       // Check if getElementById returns elements by name\r
+       // Check if getElementsByName privileges form controls or returns elements by ID\r
+       assertUsableName = assert(function( div ) {\r
+               // Inject content\r
+               div.id = expando + 0;\r
+               div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";\r
+               docElem.insertBefore( div, docElem.firstChild );\r
+\r
+               // Test\r
+               var pass = document.getElementsByName &&\r
+                       // buggy browsers will return fewer than the correct 2\r
+                       document.getElementsByName( expando ).length ===\r
+                       // buggy browsers will return more than the correct 0\r
+                       2 + document.getElementsByName( expando + 0 ).length;\r
+               assertGetIdNotName = !document.getElementById( expando );\r
+\r
+               // Cleanup\r
+               docElem.removeChild( div );\r
+\r
+               return pass;\r
+       }),\r
+\r
+       // Check if the browser returns only elements\r
+       // when doing getElementsByTagName("*")\r
+       assertTagNameNoComments = assert(function( div ) {\r
+               div.appendChild( document.createComment("") );\r
+               return div.getElementsByTagName("*").length === 0;\r
+       }),\r
+\r
+       // Check if getAttribute returns normalized href attributes\r
+       assertHrefNotNormalized = assert(function( div ) {\r
+               div.innerHTML = "<a href='#'></a>";\r
+               return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&\r
+                       div.firstChild.getAttribute("href") === "#";\r
+       }),\r
+\r
+       // Check if getElementsByClassName can be trusted\r
+       assertUsableClassName = assert(function( div ) {\r
+               // Opera can't find a second classname (in 9.6)\r
+               div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";\r
+               if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {\r
+                       return false;\r
+               }\r
+\r
+               // Safari caches class attributes, doesn't catch changes (in 3.2)\r
+               div.lastChild.className = "e";\r
+               return div.getElementsByClassName("e").length !== 1;\r
+       });\r
+\r
+var Sizzle = function( selector, context, results, seed ) {\r
+       results = results || [];\r
+       context = context || document;\r
+       var match, elem, xml, m,\r
+               nodeType = context.nodeType;\r
+\r
+       if ( nodeType !== 1 && nodeType !== 9 ) {\r
+               return [];\r
+       }\r
+\r
+       if ( !selector || typeof selector !== "string" ) {\r
+               return results;\r
+       }\r
+\r
+       xml = isXML( context );\r
+\r
+       if ( !xml && !seed ) {\r
+               if ( (match = rquickExpr.exec( selector )) ) {\r
+                       // Speed-up: Sizzle("#ID")\r
+                       if ( (m = match[1]) ) {\r
+                               if ( nodeType === 9 ) {\r
+                                       elem = context.getElementById( m );\r
+                                       // Check parentNode to catch when Blackberry 4.6 returns\r
+                                       // nodes that are no longer in the document #6963\r
+                                       if ( elem && elem.parentNode ) {\r
+                                               // Handle the case where IE, Opera, and Webkit return items\r
+                                               // by name instead of ID\r
+                                               if ( elem.id === m ) {\r
+                                                       results.push( elem );\r
+                                                       return results;\r
+                                               }\r
+                                       } else {\r
+                                               return results;\r
+                                       }\r
+                               } else {\r
+                                       // Context is not a document\r
+                                       if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\r
+                                               contains( context, elem ) && elem.id === m ) {\r
+                                               results.push( elem );\r
+                                               return results;\r
+                                       }\r
+                               }\r
+\r
+                       // Speed-up: Sizzle("TAG")\r
+                       } else if ( match[2] ) {\r
+                               push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );\r
+                               return results;\r
+\r
+                       // Speed-up: Sizzle(".CLASS")\r
+                       } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {\r
+                               push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );\r
+                               return results;\r
+                       }\r
+               }\r
+       }\r
+\r
+       // All others\r
+       return select( selector, context, results, seed, xml );\r
+};\r
+\r
+var Expr = Sizzle.selectors = {\r
+\r
+       // Can be adjusted by the user\r
+       cacheLength: 50,\r
+\r
+       match: matchExpr,\r
+\r
+       order: [ "ID", "TAG" ],\r
+\r
+       attrHandle: {},\r
+\r
+       createPseudo: markFunction,\r
+\r
+       find: {\r
+               "ID": assertGetIdNotName ?\r
+                       function( id, context, xml ) {\r
+                               if ( typeof context.getElementById !== strundefined && !xml ) {\r
+                                       var m = context.getElementById( id );\r
+                                       // Check parentNode to catch when Blackberry 4.6 returns\r
+                                       // nodes that are no longer in the document #6963\r
+                                       return m && m.parentNode ? [m] : [];\r
+                               }\r
+                       } :\r
+                       function( id, context, xml ) {\r
+                               if ( typeof context.getElementById !== strundefined && !xml ) {\r
+                                       var m = context.getElementById( id );\r
+\r
+                                       return m ?\r
+                                               m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?\r
+                                                       [m] :\r
+                                                       undefined :\r
+                                               [];\r
+                               }\r
+                       },\r
+\r
+               "TAG": assertTagNameNoComments ?\r
+                       function( tag, context ) {\r
+                               if ( typeof context.getElementsByTagName !== strundefined ) {\r
+                                       return context.getElementsByTagName( tag );\r
+                               }\r
+                       } :\r
+                       function( tag, context ) {\r
+                               var results = context.getElementsByTagName( tag );\r
+\r
+                               // Filter out possible comments\r
+                               if ( tag === "*" ) {\r
+                                       var elem,\r
+                                               tmp = [],\r
+                                               i = 0;\r
+\r
+                                       for ( ; (elem = results[i]); i++ ) {\r
+                                               if ( elem.nodeType === 1 ) {\r
+                                                       tmp.push( elem );\r
+                                               }\r
+                                       }\r
+\r
+                                       return tmp;\r
+                               }\r
+                               return results;\r
+                       }\r
+       },\r
+\r
+       relative: {\r
+               ">": { dir: "parentNode", first: true },\r
+               " ": { dir: "parentNode" },\r
+               "+": { dir: "previousSibling", first: true },\r
+               "~": { dir: "previousSibling" }\r
+       },\r
+\r
+       preFilter: {\r
+               "ATTR": function( match ) {\r
+                       match[1] = match[1].replace( rbackslash, "" );\r
+\r
+                       // Move the given value to match[3] whether quoted or unquoted\r
+                       match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );\r
+\r
+                       if ( match[2] === "~=" ) {\r
+                               match[3] = " " + match[3] + " ";\r
+                       }\r
+\r
+                       return match.slice( 0, 4 );\r
+               },\r
+\r
+               "CHILD": function( match ) {\r
+                       /* matches from matchExpr.CHILD\r
+                               1 type (only|nth|...)\r
+                               2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)\r
+                               3 xn-component of xn+y argument ([+-]?\d*n|)\r
+                               4 sign of xn-component\r
+                               5 x of xn-component\r
+                               6 sign of y-component\r
+                               7 y of y-component\r
+                       */\r
+                       match[1] = match[1].toLowerCase();\r
+\r
+                       if ( match[1] === "nth" ) {\r
+                               // nth-child requires argument\r
+                               if ( !match[2] ) {\r
+                                       Sizzle.error( match[0] );\r
+                               }\r
+\r
+                               // numeric x and y parameters for Expr.filter.CHILD\r
+                               // remember that false/true cast respectively to 0/1\r
+                               match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );\r
+                               match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );\r
+\r
+                       // other types prohibit arguments\r
+                       } else if ( match[2] ) {\r
+                               Sizzle.error( match[0] );\r
+                       }\r
+\r
+                       return match;\r
+               },\r
+\r
+               "PSEUDO": function( match ) {\r
+                       var argument,\r
+                               unquoted = match[4];\r
+\r
+                       if ( matchExpr["CHILD"].test( match[0] ) ) {\r
+                               return null;\r
+                       }\r
+\r
+                       // Relinquish our claim on characters in `unquoted` from a closing parenthesis on\r
+                       if ( unquoted && (argument = rselector.exec( unquoted )) && argument.pop() ) {\r
+\r
+                               match[0] = match[0].slice( 0, argument[0].length - unquoted.length - 1 );\r
+                               unquoted = argument[0].slice( 0, -1 );\r
+                       }\r
+\r
+                       // Quoted or unquoted, we have the full argument\r
+                       // Return only captures needed by the pseudo filter method (type and argument)\r
+                       match.splice( 2, 3, unquoted || match[3] );\r
+                       return match;\r
+               }\r
+       },\r
+\r
+       filter: {\r
+               "ID": assertGetIdNotName ?\r
+                       function( id ) {\r
+                               id = id.replace( rbackslash, "" );\r
+                               return function( elem ) {\r
+                                       return elem.getAttribute("id") === id;\r
+                               };\r
+                       } :\r
+                       function( id ) {\r
+                               id = id.replace( rbackslash, "" );\r
+                               return function( elem ) {\r
+                                       var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");\r
+                                       return node && node.value === id;\r
+                               };\r
+                       },\r
+\r
+               "TAG": function( nodeName ) {\r
+                       if ( nodeName === "*" ) {\r
+                               return function() { return true; };\r
+                       }\r
+                       nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();\r
+\r
+                       return function( elem ) {\r
+                               return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\r
+                       };\r
+               },\r
+\r
+               "CLASS": function( className ) {\r
+                       var pattern = classCache[ className ];\r
+                       if ( !pattern ) {\r
+                               pattern = classCache[ className ] = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" );\r
+                               cachedClasses.push( className );\r
+                               // Avoid too large of a cache\r
+                               if ( cachedClasses.length > Expr.cacheLength ) {\r
+                                       delete classCache[ cachedClasses.shift() ];\r
+                               }\r
+                       }\r
+                       return function( elem ) {\r
+                               return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );\r
+                       };\r
+               },\r
+\r
+               "ATTR": function( name, operator, check ) {\r
+                       if ( !operator ) {\r
+                               return function( elem ) {\r
+                                       return Sizzle.attr( elem, name ) != null;\r
+                               };\r
+                       }\r
+\r
+                       return function( elem ) {\r
+                               var result = Sizzle.attr( elem, name ),\r
+                                       value = result + "";\r
+\r
+                               if ( result == null ) {\r
+                                       return operator === "!=";\r
+                               }\r
+\r
+                               switch ( operator ) {\r
+                                       case "=":\r
+                                               return value === check;\r
+                                       case "!=":\r
+                                               return value !== check;\r
+                                       case "^=":\r
+                                               return check && value.indexOf( check ) === 0;\r
+                                       case "*=":\r
+                                               return check && value.indexOf( check ) > -1;\r
+                                       case "$=":\r
+                                               return check && value.substr( value.length - check.length ) === check;\r
+                                       case "~=":\r
+                                               return ( " " + value + " " ).indexOf( check ) > -1;\r
+                                       case "|=":\r
+                                               return value === check || value.substr( 0, check.length + 1 ) === check + "-";\r
+                               }\r
+                       };\r
+               },\r
+\r
+               "CHILD": function( type, argument, first, last ) {\r
+\r
+                       if ( type === "nth" ) {\r
+                               var doneName = done++;\r
+\r
+                               return function( elem ) {\r
+                                       var parent, diff,\r
+                                               count = 0,\r
+                                               node = elem;\r
+\r
+                                       if ( first === 1 && last === 0 ) {\r
+                                               return true;\r
+                                       }\r
+\r
+                                       parent = elem.parentNode;\r
+\r
+                                       if ( parent && (parent[ expando ] !== doneName || !elem.sizset) ) {\r
+                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {\r
+                                                       if ( node.nodeType === 1 ) {\r
+                                                               node.sizset = ++count;\r
+                                                               if ( node === elem ) {\r
+                                                                       break;\r
+                                                               }\r
+                                                       }\r
+                                               }\r
+\r
+                                               parent[ expando ] = doneName;\r
+                                       }\r
+\r
+                                       diff = elem.sizset - last;\r
+\r
+                                       if ( first === 0 ) {\r
+                                               return diff === 0;\r
+\r
+                                       } else {\r
+                                               return ( diff % first === 0 && diff / first >= 0 );\r
+                                       }\r
+                               };\r
+                       }\r
+\r
+                       return function( elem ) {\r
+                               var node = elem;\r
+\r
+                               switch ( type ) {\r
+                                       case "only":\r
+                                       case "first":\r
+                                               while ( (node = node.previousSibling) ) {\r
+                                                       if ( node.nodeType === 1 ) {\r
+                                                               return false;\r
+                                                       }\r
+                                               }\r
+\r
+                                               if ( type === "first" ) {\r
+                                                       return true;\r
+                                               }\r
+\r
+                                               node = elem;\r
+\r
+                                               /* falls through */\r
+                                       case "last":\r
+                                               while ( (node = node.nextSibling) ) {\r
+                                                       if ( node.nodeType === 1 ) {\r
+                                                               return false;\r
+                                                       }\r
+                                               }\r
+\r
+                                               return true;\r
+                               }\r
+                       };\r
+               },\r
+\r
+               "PSEUDO": function( pseudo, argument, context, xml ) {\r
+                       // pseudo-class names are case-insensitive\r
+                       // http://www.w3.org/TR/selectors/#pseudo-classes\r
+                       // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\r
+                       var fn = Expr.pseudos[ pseudo ] || Expr.pseudos[ pseudo.toLowerCase() ];\r
+\r
+                       if ( !fn ) {\r
+                               Sizzle.error( "unsupported pseudo: " + pseudo );\r
+                       }\r
+\r
+                       // The user may set fn.sizzleFilter to indicate\r
+                       // that arguments are needed to create the filter function\r
+                       // just as Sizzle does\r
+                       if ( !fn.sizzleFilter ) {\r
+                               return fn;\r
+                       }\r
+\r
+                       return fn( argument, context, xml );\r
+               }\r
+       },\r
+\r
+       pseudos: {\r
+               "not": markFunction(function( selector, context, xml ) {\r
+                       // Trim the selector passed to compile\r
+                       // to avoid treating leading and trailing\r
+                       // spaces as combinators\r
+                       var matcher = compile( selector.replace( rtrim, "$1" ), context, xml );\r
+                       return function( elem ) {\r
+                               return !matcher( elem );\r
+                       };\r
+               }),\r
+\r
+               "enabled": function( elem ) {\r
+                       return elem.disabled === false;\r
+               },\r
+\r
+               "disabled": function( elem ) {\r
+                       return elem.disabled === true;\r
+               },\r
+\r
+               "checked": function( elem ) {\r
+                       // In CSS3, :checked should return both checked and selected elements\r
+                       // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\r
+                       var nodeName = elem.nodeName.toLowerCase();\r
+                       return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);\r
+               },\r
+\r
+               "selected": function( elem ) {\r
+                       // Accessing this property makes selected-by-default\r
+                       // options in Safari work properly\r
+                       if ( elem.parentNode ) {\r
+                               elem.parentNode.selectedIndex;\r
+                       }\r
+\r
+                       return elem.selected === true;\r
+               },\r
+\r
+               "parent": function( elem ) {\r
+                       return !Expr.pseudos["empty"]( elem );\r
+               },\r
+\r
+               "empty": function( elem ) {\r
+                       // http://www.w3.org/TR/selectors/#empty-pseudo\r
+                       // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),\r
+                       //   not comment, processing instructions, or others\r
+                       // Thanks to Diego Perini for the nodeName shortcut\r
+                       //   Greater than "@" means alpha characters (specifically not starting with "#" or "?")\r
+                       var nodeType;\r
+                       elem = elem.firstChild;\r
+                       while ( elem ) {\r
+                               if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {\r
+                                       return false;\r
+                               }\r
+                               elem = elem.nextSibling;\r
+                       }\r
+                       return true;\r
+               },\r
+\r
+               "contains": markFunction(function( text ) {\r
+                       return function( elem ) {\r
+                               return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\r
+                       };\r
+               }),\r
+\r
+               "has": markFunction(function( selector ) {\r
+                       return function( elem ) {\r
+                               return Sizzle( selector, elem ).length > 0;\r
+                       };\r
+               }),\r
+\r
+               "header": function( elem ) {\r
+                       return rheader.test( elem.nodeName );\r
+               },\r
+\r
+               "text": function( elem ) {\r
+                       var type, attr;\r
+                       // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)\r
+                       // use getAttribute instead to test this case\r
+                       return elem.nodeName.toLowerCase() === "input" &&\r
+                               (type = elem.type) === "text" &&\r
+                               ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );\r
+               },\r
+\r
+               // Input types\r
+               "radio": createInputFunction("radio"),\r
+               "checkbox": createInputFunction("checkbox"),\r
+               "file": createInputFunction("file"),\r
+               "password": createInputFunction("password"),\r
+               "image": createInputFunction("image"),\r
+\r
+               "submit": createButtonFunction("submit"),\r
+               "reset": createButtonFunction("reset"),\r
+\r
+               "button": function( elem ) {\r
+                       var name = elem.nodeName.toLowerCase();\r
+                       return name === "input" && elem.type === "button" || name === "button";\r
+               },\r
+\r
+               "input": function( elem ) {\r
+                       return rinputs.test( elem.nodeName );\r
+               },\r
+\r
+               "focus": function( elem ) {\r
+                       var doc = elem.ownerDocument;\r
+                       return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href);\r
+               },\r
+\r
+               "active": function( elem ) {\r
+                       return elem === elem.ownerDocument.activeElement;\r
+               }\r
+       },\r
+\r
+       setFilters: {\r
+               "first": function( elements, argument, not ) {\r
+                       return not ? elements.slice( 1 ) : [ elements[0] ];\r
+               },\r
+\r
+               "last": function( elements, argument, not ) {\r
+                       var elem = elements.pop();\r
+                       return not ? elements : [ elem ];\r
+               },\r
+\r
+               "even": function( elements, argument, not ) {\r
+                       var results = [],\r
+                               i = not ? 1 : 0,\r
+                               len = elements.length;\r
+                       for ( ; i < len; i = i + 2 ) {\r
+                               results.push( elements[i] );\r
+                       }\r
+                       return results;\r
+               },\r
+\r
+               "odd": function( elements, argument, not ) {\r
+                       var results = [],\r
+                               i = not ? 0 : 1,\r
+                               len = elements.length;\r
+                       for ( ; i < len; i = i + 2 ) {\r
+                               results.push( elements[i] );\r
+                       }\r
+                       return results;\r
+               },\r
+\r
+               "lt": function( elements, argument, not ) {\r
+                       return not ? elements.slice( +argument ) : elements.slice( 0, +argument );\r
+               },\r
+\r
+               "gt": function( elements, argument, not ) {\r
+                       return not ? elements.slice( 0, +argument + 1 ) : elements.slice( +argument + 1 );\r
+               },\r
+\r
+               "eq": function( elements, argument, not ) {\r
+                       var elem = elements.splice( +argument, 1 );\r
+                       return not ? elements : elem;\r
+               }\r
+       }\r
+};\r
+\r
+// Deprecated\r
+Expr.setFilters["nth"] = Expr.setFilters["eq"];\r
+\r
+// Back-compat\r
+Expr.filters = Expr.pseudos;\r
+\r
+// IE6/7 return a modified href\r
+if ( !assertHrefNotNormalized ) {\r
+       Expr.attrHandle = {\r
+               "href": function( elem ) {\r
+                       return elem.getAttribute( "href", 2 );\r
+               },\r
+               "type": function( elem ) {\r
+                       return elem.getAttribute("type");\r
+               }\r
+       };\r
+}\r
+\r
+// Add getElementsByName if usable\r
+if ( assertUsableName ) {\r
+       Expr.order.push("NAME");\r
+       Expr.find["NAME"] = function( name, context ) {\r
+               if ( typeof context.getElementsByName !== strundefined ) {\r
+                       return context.getElementsByName( name );\r
+               }\r
+       };\r
+}\r
+\r
+// Add getElementsByClassName if usable\r
+if ( assertUsableClassName ) {\r
+       Expr.order.splice( 1, 0, "CLASS" );\r
+       Expr.find["CLASS"] = function( className, context, xml ) {\r
+               if ( typeof context.getElementsByClassName !== strundefined && !xml ) {\r
+                       return context.getElementsByClassName( className );\r
+               }\r
+       };\r
+}\r
+\r
+// If slice is not available, provide a backup\r
+try {\r
+       slice.call( docElem.childNodes, 0 )[0].nodeType;\r
+} catch ( e ) {\r
+       slice = function( i ) {\r
+               var elem, results = [];\r
+               for ( ; (elem = this[i]); i++ ) {\r
+                       results.push( elem );\r
+               }\r
+               return results;\r
+       };\r
+}\r
+\r
+var isXML = Sizzle.isXML = function( elem ) {\r
+       // documentElement is verified for cases where it doesn't yet exist\r
+       // (such as loading iframes in IE - #4833)\r
+       var documentElement = elem && (elem.ownerDocument || elem).documentElement;\r
+       return documentElement ? documentElement.nodeName !== "HTML" : false;\r
+};\r
+\r
+// Element contains another\r
+var contains = Sizzle.contains = docElem.compareDocumentPosition ?\r
+       function( a, b ) {\r
+               return !!( a.compareDocumentPosition( b ) & 16 );\r
+       } :\r
+       docElem.contains ?\r
+       function( a, b ) {\r
+               var adown = a.nodeType === 9 ? a.documentElement : a,\r
+                       bup = b.parentNode;\r
+               return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );\r
+       } :\r
+       function( a, b ) {\r
+               while ( (b = b.parentNode) ) {\r
+                       if ( b === a ) {\r
+                               return true;\r
+                       }\r
+               }\r
+               return false;\r
+       };\r
+\r
+/**\r
+ * Utility function for retrieving the text value of an array of DOM nodes\r
+ * @param {Array|Element} elem\r
+ */\r
+var getText = Sizzle.getText = function( elem ) {\r
+       var node,\r
+               ret = "",\r
+               i = 0,\r
+               nodeType = elem.nodeType;\r
+\r
+       if ( nodeType ) {\r
+               if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\r
+                       // Use textContent for elements\r
+                       // innerText usage removed for consistency of new lines (see #11153)\r
+                       if ( typeof elem.textContent === "string" ) {\r
+                               return elem.textContent;\r
+                       } else {\r
+                               // Traverse its children\r
+                               for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\r
+                                       ret += getText( elem );\r
+                               }\r
+                       }\r
+               } else if ( nodeType === 3 || nodeType === 4 ) {\r
+                       return elem.nodeValue;\r
+               }\r
+               // Do not include comment or processing instruction nodes\r
+       } else {\r
+\r
+               // If no nodeType, this is expected to be an array\r
+               for ( ; (node = elem[i]); i++ ) {\r
+                       // Do not traverse comment nodes\r
+                       ret += getText( node );\r
+               }\r
+       }\r
+       return ret;\r
+};\r
+\r
+Sizzle.attr = function( elem, name ) {\r
+       var attr,\r
+               xml = isXML( elem );\r
+\r
+       if ( !xml ) {\r
+               name = name.toLowerCase();\r
+       }\r
+       if ( Expr.attrHandle[ name ] ) {\r
+               return Expr.attrHandle[ name ]( elem );\r
+       }\r
+       if ( assertAttributes || xml ) {\r
+               return elem.getAttribute( name );\r
+       }\r
+       attr = elem.getAttributeNode( name );\r
+       return attr ?\r
+               typeof elem[ name ] === "boolean" ?\r
+                       elem[ name ] ? name : null :\r
+                       attr.specified ? attr.value : null :\r
+               null;\r
+};\r
+\r
+Sizzle.error = function( msg ) {\r
+       throw new Error( "Syntax error, unrecognized expression: " + msg );\r
+};\r
+\r
+// Check if the JavaScript engine is using some sort of\r
+// optimization where it does not always call our comparision\r
+// function. If that is the case, discard the hasDuplicate value.\r
+//   Thus far that includes Google Chrome.\r
+[0, 0].sort(function() {\r
+       return (baseHasDuplicate = 0);\r
+});\r
+\r
+\r
+if ( docElem.compareDocumentPosition ) {\r
+       sortOrder = function( a, b ) {\r
+               if ( a === b ) {\r
+                       hasDuplicate = true;\r
+                       return 0;\r
+               }\r
+\r
+               return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?\r
+                       a.compareDocumentPosition :\r
+                       a.compareDocumentPosition(b) & 4\r
+               ) ? -1 : 1;\r
+       };\r
+\r
+} else {\r
+       sortOrder = function( a, b ) {\r
+               // The nodes are identical, we can exit early\r
+               if ( a === b ) {\r
+                       hasDuplicate = true;\r
+                       return 0;\r
+\r
+               // Fallback to using sourceIndex (in IE) if it's available on both nodes\r
+               } else if ( a.sourceIndex && b.sourceIndex ) {\r
+                       return a.sourceIndex - b.sourceIndex;\r
+               }\r
+\r
+               var al, bl,\r
+                       ap = [],\r
+                       bp = [],\r
+                       aup = a.parentNode,\r
+                       bup = b.parentNode,\r
+                       cur = aup;\r
+\r
+               // If the nodes are siblings (or identical) we can do a quick check\r
+               if ( aup === bup ) {\r
+                       return siblingCheck( a, b );\r
+\r
+               // If no parents were found then the nodes are disconnected\r
+               } else if ( !aup ) {\r
+                       return -1;\r
+\r
+               } else if ( !bup ) {\r
+                       return 1;\r
+               }\r
+\r
+               // Otherwise they're somewhere else in the tree so we need\r
+               // to build up a full list of the parentNodes for comparison\r
+               while ( cur ) {\r
+                       ap.unshift( cur );\r
+                       cur = cur.parentNode;\r
+               }\r
+\r
+               cur = bup;\r
+\r
+               while ( cur ) {\r
+                       bp.unshift( cur );\r
+                       cur = cur.parentNode;\r
+               }\r
+\r
+               al = ap.length;\r
+               bl = bp.length;\r
+\r
+               // Start walking down the tree looking for a discrepancy\r
+               for ( var i = 0; i < al && i < bl; i++ ) {\r
+                       if ( ap[i] !== bp[i] ) {\r
+                               return siblingCheck( ap[i], bp[i] );\r
+                       }\r
+               }\r
+\r
+               // We ended someplace up the tree so do a sibling check\r
+               return i === al ?\r
+                       siblingCheck( a, bp[i], -1 ) :\r
+                       siblingCheck( ap[i], b, 1 );\r
+       };\r
+\r
+       siblingCheck = function( a, b, ret ) {\r
+               if ( a === b ) {\r
+                       return ret;\r
+               }\r
+\r
+               var cur = a.nextSibling;\r
+\r
+               while ( cur ) {\r
+                       if ( cur === b ) {\r
+                               return -1;\r
+                       }\r
+\r
+                       cur = cur.nextSibling;\r
+               }\r
+\r
+               return 1;\r
+       };\r
+}\r
+\r
+// Document sorting and removing duplicates\r
+Sizzle.uniqueSort = function( results ) {\r
+       var elem,\r
+               i = 1;\r
+\r
+       if ( sortOrder ) {\r
+               hasDuplicate = baseHasDuplicate;\r
+               results.sort( sortOrder );\r
+\r
+               if ( hasDuplicate ) {\r
+                       for ( ; (elem = results[i]); i++ ) {\r
+                               if ( elem === results[ i - 1 ] ) {\r
+                                       results.splice( i--, 1 );\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       return results;\r
+};\r
+\r
+function multipleContexts( selector, contexts, results, seed ) {\r
+       var i = 0,\r
+               len = contexts.length;\r
+       for ( ; i < len; i++ ) {\r
+               Sizzle( selector, contexts[i], results, seed );\r
+       }\r
+}\r
+\r
+function handlePOSGroup( selector, posfilter, argument, contexts, seed, not ) {\r
+       var results,\r
+               fn = Expr.setFilters[ posfilter.toLowerCase() ];\r
+\r
+       if ( !fn ) {\r
+               Sizzle.error( posfilter );\r
+       }\r
+\r
+       if ( selector || !(results = seed) ) {\r
+               multipleContexts( selector || "*", contexts, (results = []), seed );\r
+       }\r
+\r
+       return results.length > 0 ? fn( results, argument, not ) : [];\r
+}\r
+\r
+function handlePOS( selector, context, results, seed, groups ) {\r
+       var match, not, anchor, ret, elements, currentContexts, part, lastIndex,\r
+               i = 0,\r
+               len = groups.length,\r
+               rpos = matchExpr["POS"],\r
+               // This is generated here in case matchExpr["POS"] is extended\r
+               rposgroups = new RegExp( "^" + rpos.source + "(?!" + whitespace + ")", "i" ),\r
+               // This is for making sure non-participating\r
+               // matching groups are represented cross-browser (IE6-8)\r
+               setUndefined = function() {\r
+                       var i = 1,\r
+                               len = arguments.length - 2;\r
+                       for ( ; i < len; i++ ) {\r
+                               if ( arguments[i] === undefined ) {\r
+                                       match[i] = undefined;\r
+                               }\r
+                       }\r
+               };\r
+\r
+       for ( ; i < len; i++ ) {\r
+               // Reset regex index to 0\r
+               rpos.exec("");\r
+               selector = groups[i];\r
+               ret = [];\r
+               anchor = 0;\r
+               elements = seed;\r
+               while ( (match = rpos.exec( selector )) ) {\r
+                       lastIndex = rpos.lastIndex = match.index + match[0].length;\r
+                       if ( lastIndex > anchor ) {\r
+                               part = selector.slice( anchor, match.index );\r
+                               anchor = lastIndex;\r
+                               currentContexts = [ context ];\r
+\r
+                               if ( rcombinators.test(part) ) {\r
+                                       if ( elements ) {\r
+                                               currentContexts = elements;\r
+                                       }\r
+                                       elements = seed;\r
+                               }\r
+\r
+                               if ( (not = rendsWithNot.test( part )) ) {\r
+                                       part = part.slice( 0, -5 ).replace( rcombinators, "$&*" );\r
+                               }\r
+\r
+                               if ( match.length > 1 ) {\r
+                                       match[0].replace( rposgroups, setUndefined );\r
+                               }\r
+                               elements = handlePOSGroup( part, match[1], match[2], currentContexts, elements, not );\r
+                       }\r
+               }\r
+\r
+               if ( elements ) {\r
+                       ret = ret.concat( elements );\r
+\r
+                       if ( (part = selector.slice( anchor )) && part !== ")" ) {\r
+                               if ( rcombinators.test(part) ) {\r
+                                       multipleContexts( part, ret, results, seed );\r
+                               } else {\r
+                                       Sizzle( part, context, results, seed ? seed.concat(elements) : elements );\r
+                               }\r
+                       } else {\r
+                               push.apply( results, ret );\r
+                       }\r
+               } else {\r
+                       Sizzle( selector, context, results, seed );\r
+               }\r
+       }\r
+\r
+       // Do not sort if this is a single filter\r
+       return len === 1 ? results : Sizzle.uniqueSort( results );\r
+}\r
+\r
+function tokenize( selector, context, xml ) {\r
+       var tokens, soFar, type,\r
+               groups = [],\r
+               i = 0,\r
+\r
+               // Catch obvious selector issues: terminal ")"; nonempty fallback match\r
+               // rselector never fails to match *something*\r
+               match = rselector.exec( selector ),\r
+               matched = !match.pop() && !match.pop(),\r
+               selectorGroups = matched && selector.match( rgroups ) || [""],\r
+\r
+               preFilters = Expr.preFilter,\r
+               filters = Expr.filter,\r
+               checkContext = !xml && context !== document;\r
+\r
+       for ( ; (soFar = selectorGroups[i]) != null && matched; i++ ) {\r
+               groups.push( tokens = [] );\r
+\r
+               // Need to make sure we're within a narrower context if necessary\r
+               // Adding a descendant combinator will generate what is needed\r
+               if ( checkContext ) {\r
+                       soFar = " " + soFar;\r
+               }\r
+\r
+               while ( soFar ) {\r
+                       matched = false;\r
+\r
+                       // Combinators\r
+                       if ( (match = rcombinators.exec( soFar )) ) {\r
+                               soFar = soFar.slice( match[0].length );\r
+\r
+                               // Cast descendant combinators to space\r
+                               matched = tokens.push({ part: match.pop().replace( rtrim, " " ), captures: match });\r
+                       }\r
+\r
+                       // Filters\r
+                       for ( type in filters ) {\r
+                               if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\r
+                                       (match = preFilters[ type ]( match, context, xml )) ) ) {\r
+\r
+                                       soFar = soFar.slice( match.shift().length );\r
+                                       matched = tokens.push({ part: type, captures: match });\r
+                               }\r
+                       }\r
+\r
+                       if ( !matched ) {\r
+                               break;\r
+                       }\r
+               }\r
+       }\r
+\r
+       if ( !matched ) {\r
+               Sizzle.error( selector );\r
+       }\r
+\r
+       return groups;\r
+}\r
+\r
+function addCombinator( matcher, combinator, context ) {\r
+       var dir = combinator.dir,\r
+               doneName = done++;\r
+\r
+       if ( !matcher ) {\r
+               // If there is no matcher to check, check against the context\r
+               matcher = function( elem ) {\r
+                       return elem === context;\r
+               };\r
+       }\r
+       return combinator.first ?\r
+               function( elem, context ) {\r
+                       while ( (elem = elem[ dir ]) ) {\r
+                               if ( elem.nodeType === 1 ) {\r
+                                       return matcher( elem, context ) && elem;\r
+                               }\r
+                       }\r
+               } :\r
+               function( elem, context ) {\r
+                       var cache,\r
+                               dirkey = doneName + "." + dirruns,\r
+                               cachedkey = dirkey + "." + cachedruns;\r
+                       while ( (elem = elem[ dir ]) ) {\r
+                               if ( elem.nodeType === 1 ) {\r
+                                       if ( (cache = elem[ expando ]) === cachedkey ) {\r
+                                               return elem.sizset;\r
+                                       } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {\r
+                                               if ( elem.sizset ) {\r
+                                                       return elem;\r
+                                               }\r
+                                       } else {\r
+                                               elem[ expando ] = cachedkey;\r
+                                               if ( matcher( elem, context ) ) {\r
+                                                       elem.sizset = true;\r
+                                                       return elem;\r
+                                               }\r
+                                               elem.sizset = false;\r
+                                       }\r
+                               }\r
+                       }\r
+               };\r
+}\r
+\r
+function addMatcher( higher, deeper ) {\r
+       return higher ?\r
+               function( elem, context ) {\r
+                       var result = deeper( elem, context );\r
+                       return result && higher( result === true ? elem : result, context );\r
+               } :\r
+               deeper;\r
+}\r
+\r
+// ["TAG", ">", "ID", " ", "CLASS"]\r
+function matcherFromTokens( tokens, context, xml ) {\r
+       var token, matcher,\r
+               i = 0;\r
+\r
+       for ( ; (token = tokens[i]); i++ ) {\r
+               if ( Expr.relative[ token.part ] ) {\r
+                       matcher = addCombinator( matcher, Expr.relative[ token.part ], context );\r
+               } else {\r
+                       token.captures.push( context, xml );\r
+                       matcher = addMatcher( matcher, Expr.filter[ token.part ].apply( null, token.captures ) );\r
+               }\r
+       }\r
+\r
+       return matcher;\r
+}\r
+\r
+function matcherFromGroupMatchers( matchers ) {\r
+       return function( elem, context ) {\r
+               var matcher,\r
+                       j = 0;\r
+               for ( ; (matcher = matchers[j]); j++ ) {\r
+                       if ( matcher(elem, context) ) {\r
+                               return true;\r
+                       }\r
+               }\r
+               return false;\r
+       };\r
+}\r
+\r
+var compile = Sizzle.compile = function( selector, context, xml ) {\r
+       var tokens, group, i,\r
+               cached = compilerCache[ selector ];\r
+\r
+       // Return a cached group function if already generated (context dependent)\r
+       if ( cached && cached.context === context ) {\r
+               return cached;\r
+       }\r
+\r
+       // Generate a function of recursive functions that can be used to check each element\r
+       group = tokenize( selector, context, xml );\r
+       for ( i = 0; (tokens = group[i]); i++ ) {\r
+               group[i] = matcherFromTokens( tokens, context, xml );\r
+       }\r
+\r
+       // Cache the compiled function\r
+       cached = compilerCache[ selector ] = matcherFromGroupMatchers( group );\r
+       cached.context = context;\r
+       cached.runs = cached.dirruns = 0;\r
+       cachedSelectors.push( selector );\r
+       // Ensure only the most recent are cached\r
+       if ( cachedSelectors.length > Expr.cacheLength ) {\r
+               delete compilerCache[ cachedSelectors.shift() ];\r
+       }\r
+       return cached;\r
+};\r
+\r
+Sizzle.matches = function( expr, elements ) {\r
+       return Sizzle( expr, null, null, elements );\r
+};\r
+\r
+Sizzle.matchesSelector = function( elem, expr ) {\r
+       return Sizzle( expr, null, null, [ elem ] ).length > 0;\r
+};\r
+\r
+var select = function( selector, context, results, seed, xml ) {\r
+       // Remove excessive whitespace\r
+       selector = selector.replace( rtrim, "$1" );\r
+       var elements, matcher, i, len, elem, token,\r
+               type, findContext, notTokens,\r
+               match = selector.match( rgroups ),\r
+               tokens = selector.match( rtokens ),\r
+               contextNodeType = context.nodeType;\r
+\r
+       // POS handling\r
+       if ( matchExpr["POS"].test(selector) ) {\r
+               return handlePOS( selector, context, results, seed, match );\r
+       }\r
+\r
+       if ( seed ) {\r
+               elements = slice.call( seed, 0 );\r
+\r
+       // To maintain document order, only narrow the\r
+       // set if there is one group\r
+       } else if ( match && match.length === 1 ) {\r
+\r
+               // Take a shortcut and set the context if the root selector is an ID\r
+               if ( tokens.length > 1 && contextNodeType === 9 && !xml &&\r
+                               (match = matchExpr["ID"].exec( tokens[0] )) ) {\r
+\r
+                       context = Expr.find["ID"]( match[1], context, xml )[0];\r
+                       if ( !context ) {\r
+                               return results;\r
+                       }\r
+\r
+                       selector = selector.slice( tokens.shift().length );\r
+               }\r
+\r
+               findContext = ( (match = rsibling.exec( tokens[0] )) && !match.index && context.parentNode ) || context;\r
+\r
+               // Get the last token, excluding :not\r
+               notTokens = tokens.pop();\r
+               token = notTokens.split(":not")[0];\r
+\r
+               for ( i = 0, len = Expr.order.length; i < len; i++ ) {\r
+                       type = Expr.order[i];\r
+\r
+                       if ( (match = matchExpr[ type ].exec( token )) ) {\r
+                               elements = Expr.find[ type ]( (match[1] || "").replace( rbackslash, "" ), findContext, xml );\r
+\r
+                               if ( elements == null ) {\r
+                                       continue;\r
+                               }\r
+\r
+                               if ( token === notTokens ) {\r
+                                       selector = selector.slice( 0, selector.length - notTokens.length ) +\r
+                                               token.replace( matchExpr[ type ], "" );\r
+\r
+                                       if ( !selector ) {\r
+                                               push.apply( results, slice.call(elements, 0) );\r
+                                       }\r
+                               }\r
+                               break;\r
+                       }\r
+               }\r
+       }\r
+\r
+       // Only loop over the given elements once\r
+       // If selector is empty, we're already done\r
+       if ( selector ) {\r
+               matcher = compile( selector, context, xml );\r
+               dirruns = matcher.dirruns++;\r
+\r
+               if ( elements == null ) {\r
+                       elements = Expr.find["TAG"]( "*", (rsibling.test( selector ) && context.parentNode) || context );\r
+               }\r
+               for ( i = 0; (elem = elements[i]); i++ ) {\r
+                       cachedruns = matcher.runs++;\r
+                       if ( matcher(elem, context) ) {\r
+                               results.push( elem );\r
+                       }\r
+               }\r
+       }\r
+\r
+       return results;\r
+};\r
+\r
+if ( document.querySelectorAll ) {\r
+       (function() {\r
+               var disconnectedMatch,\r
+                       oldSelect = select,\r
+                       rescape = /'|\\/g,\r
+                       rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,\r
+                       rbuggyQSA = [],\r
+                       // matchesSelector(:active) reports false when true (IE9/Opera 11.5)\r
+                       // A support test would require too much code (would include document ready)\r
+                       // just skip matchesSelector for :active\r
+                       rbuggyMatches = [":active"],\r
+                       matches = docElem.matchesSelector ||\r
+                               docElem.mozMatchesSelector ||\r
+                               docElem.webkitMatchesSelector ||\r
+                               docElem.oMatchesSelector ||\r
+                               docElem.msMatchesSelector;\r
+\r
+               // Build QSA regex\r
+               // Regex strategy adopted from Diego Perini\r
+               assert(function( div ) {\r
+                       div.innerHTML = "<select><option selected></option></select>";\r
+\r
+                       // IE8 - Some boolean attributes are not treated correctly\r
+                       if ( !div.querySelectorAll("[selected]").length ) {\r
+                               rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );\r
+                       }\r
+\r
+                       // Webkit/Opera - :checked should return selected option elements\r
+                       // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\r
+                       // IE8 throws error here (do not put tests after this one)\r
+                       if ( !div.querySelectorAll(":checked").length ) {\r
+                               rbuggyQSA.push(":checked");\r
+                       }\r
+               });\r
+\r
+               assert(function( div ) {\r
+\r
+                       // Opera 10-12/IE9 - ^= $= *= and empty values\r
+                       // Should not select anything\r
+                       div.innerHTML = "<p test=''></p>";\r
+                       if ( div.querySelectorAll("[test^='']").length ) {\r
+                               rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );\r
+                       }\r
+\r
+                       // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\r
+                       // IE8 throws error here (do not put tests after this one)\r
+                       div.innerHTML = "<input type='hidden'>";\r
+                       if ( !div.querySelectorAll(":enabled").length ) {\r
+                               rbuggyQSA.push(":enabled", ":disabled");\r
+                       }\r
+               });\r
+\r
+               rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );\r
+\r
+               select = function( selector, context, results, seed, xml ) {\r
+                       // Only use querySelectorAll when not filtering,\r
+                       // when this is not xml,\r
+                       // and when no QSA bugs apply\r
+                       if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\r
+                               if ( context.nodeType === 9 ) {\r
+                                       try {\r
+                                               push.apply( results, slice.call(context.querySelectorAll( selector ), 0) );\r
+                                               return results;\r
+                                       } catch(qsaError) {}\r
+                               // qSA works strangely on Element-rooted queries\r
+                               // We can work around this by specifying an extra ID on the root\r
+                               // and working up from there (Thanks to Andrew Dupont for the technique)\r
+                               // IE 8 doesn't work on object elements\r
+                               } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {\r
+                                       var old = context.getAttribute("id"),\r
+                                               nid = old || expando,\r
+                                               newContext = rsibling.test( selector ) && context.parentNode || context;\r
+\r
+                                       if ( old ) {\r
+                                               nid = nid.replace( rescape, "\\$&" );\r
+                                       } else {\r
+                                               context.setAttribute( "id", nid );\r
+                                       }\r
+\r
+                                       try {\r
+                                               push.apply( results, slice.call( newContext.querySelectorAll(\r
+                                                       selector.replace( rgroups, "[id='" + nid + "'] $&" )\r
+                                               ), 0 ) );\r
+                                               return results;\r
+                                       } catch(qsaError) {\r
+                                       } finally {\r
+                                               if ( !old ) {\r
+                                                       context.removeAttribute("id");\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       return oldSelect( selector, context, results, seed, xml );\r
+               };\r
+\r
+               if ( matches ) {\r
+                       assert(function( div ) {\r
+                               // Check to see if it's possible to do matchesSelector\r
+                               // on a disconnected node (IE 9)\r
+                               disconnectedMatch = matches.call( div, "div" );\r
+\r
+                               // This should fail with an exception\r
+                               // Gecko does not error, returns false instead\r
+                               try {\r
+                                       matches.call( div, "[test!='']:sizzle" );\r
+                                       rbuggyMatches.push( Expr.match.PSEUDO );\r
+                               } catch ( e ) {}\r
+                       });\r
+\r
+                       // rbuggyMatches always contains :active, so no need for a length check\r
+                       rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );\r
+\r
+                       Sizzle.matchesSelector = function( elem, expr ) {\r
+                               // Make sure that attribute selectors are quoted\r
+                               expr = expr.replace( rattributeQuotes, "='$1']" );\r
+\r
+                               // rbuggyMatches always contains :active, so no need for an existence check\r
+                               if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) {\r
+                                       try {\r
+                                               var ret = matches.call( elem, expr );\r
+\r
+                                               // IE 9's matchesSelector returns false on disconnected nodes\r
+                                               if ( ret || disconnectedMatch ||\r
+                                                               // As well, disconnected nodes are said to be in a document\r
+                                                               // fragment in IE 9\r
+                                                               elem.document && elem.document.nodeType !== 11 ) {\r
+                                                       return ret;\r
+                                               }\r
+                                       } catch(e) {}\r
+                               }\r
+\r
+                               return Sizzle( expr, null, null, [ elem ] ).length > 0;\r
+                       };\r
+               }\r
+       })();\r
+}\r
+\r
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+\r
+\r
+})( window );\r
+var runtil = /Until$/,
+       rparentsprev = /^(?:parents|prev(?:Until|All))/,
+       isSimple = /^.[^:#\[\.,]*$/,
+       rneedsContext = jQuery.expr.match.needsContext,
+       // methods guaranteed to produce a unique set when starting from a unique set
+       guaranteedUnique = {
+               children: true,
+               contents: true,
+               next: true,
+               prev: true
+       };
 
+jQuery.fn.extend({
+       find: function( selector ) {
+               var i, l, length, n, r, ret,
+                       self = this;
 
-
-/*!
- * Sizzle CSS Selector Engine
- *  Copyright 2011, The Dojo Foundation
- *  Released under the MIT, BSD, and GPL Licenses.
- *  More information: http://sizzlejs.com/
- */
-(function(){
-
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
-       expando = "sizcache" + (Math.random() + '').replace('.', ''),
-       done = 0,
-       toString = Object.prototype.toString,
-       hasDuplicate = false,
-       baseHasDuplicate = true,
-       rBackslash = /\\/g,
-       rReturn = /\r\n/g,
-       rNonWord = /\W/;
-
-// Here we check if the JavaScript engine is using some sort of
-// optimization where it does not always call our comparision
-// function. If that is the case, discard the hasDuplicate value.
-//   Thus far that includes Google Chrome.
-[0, 0].sort(function() {
-       baseHasDuplicate = false;
-       return 0;
-});
-
-var Sizzle = function( selector, context, results, seed ) {
-       results = results || [];
-       context = context || document;
-
-       var origContext = context;
-
-       if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
-               return [];
-       }
-
-       if ( !selector || typeof selector !== "string" ) {
-               return results;
-       }
-
-       var m, set, checkSet, extra, ret, cur, pop, i,
-               prune = true,
-               contextXML = Sizzle.isXML( context ),
-               parts = [],
-               soFar = selector;
-
-       // Reset the position of the chunker regexp (start from head)
-       do {
-               chunker.exec( "" );
-               m = chunker.exec( soFar );
-
-               if ( m ) {
-                       soFar = m[3];
-
-                       parts.push( m[1] );
-
-                       if ( m[2] ) {
-                               extra = m[3];
-                               break;
-                       }
+               if ( typeof selector !== "string" ) {
+                       return jQuery( selector ).filter(function() {
+                               for ( i = 0, l = self.length; i < l; i++ ) {
+                                       if ( jQuery.contains( self[ i ], this ) ) {
+                                               return true;
+                                       }
+                               }
+                       });
                }
-       } while ( m );
-
-       if ( parts.length > 1 && origPOS.exec( selector ) ) {
 
-               if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
-                       set = posProcess( parts[0] + parts[1], context, seed );
+               ret = this.pushStack( "", "find", selector );
 
-               } else {
-                       set = Expr.relative[ parts[0] ] ?
-                               [ context ] :
-                               Sizzle( parts.shift(), context );
-
-                       while ( parts.length ) {
-                               selector = parts.shift();
+               for ( i = 0, l = this.length; i < l; i++ ) {
+                       length = ret.length;
+                       jQuery.find( selector, this[i], ret );
 
-                               if ( Expr.relative[ selector ] ) {
-                                       selector += parts.shift();
+                       if ( i > 0 ) {
+                               // Make sure that the results are unique
+                               for ( n = length; n < ret.length; n++ ) {
+                                       for ( r = 0; r < length; r++ ) {
+                                               if ( ret[r] === ret[n] ) {
+                                                       ret.splice(n--, 1);
+                                                       break;
+                                               }
+                                       }
                                }
-
-                               set = posProcess( selector, set, seed );
                        }
                }
 
-       } else {
-               // Take a shortcut and set the context if the root selector is an ID
-               // (but not if it'll be faster if the inner selector is an ID)
-               if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
-                               Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
-
-                       ret = Sizzle.find( parts.shift(), context, contextXML );
-                       context = ret.expr ?
-                               Sizzle.filter( ret.expr, ret.set )[0] :
-                               ret.set[0];
-               }
-
-               if ( context ) {
-                       ret = seed ?
-                               { expr: parts.pop(), set: makeArray(seed) } :
-                               Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
-
-                       set = ret.expr ?
-                               Sizzle.filter( ret.expr, ret.set ) :
-                               ret.set;
-
-                       if ( parts.length > 0 ) {
-                               checkSet = makeArray( set );
-
-                       } else {
-                               prune = false;
-                       }
-
-                       while ( parts.length ) {
-                               cur = parts.pop();
-                               pop = cur;
+               return ret;
+       },
 
-                               if ( !Expr.relative[ cur ] ) {
-                                       cur = "";
-                               } else {
-                                       pop = parts.pop();
-                               }
+       has: function( target ) {
+               var i,
+                       targets = jQuery( target, this ),
+                       len = targets.length;
 
-                               if ( pop == null ) {
-                                       pop = context;
+               return this.filter(function() {
+                       for ( i = 0; i < len; i++ ) {
+                               if ( jQuery.contains( this, targets[i] ) ) {
+                                       return true;
                                }
-
-                               Expr.relative[ cur ]( checkSet, pop, contextXML );
                        }
+               });
+       },
 
-               } else {
-                       checkSet = parts = [];
-               }
-       }
+       not: function( selector ) {
+               return this.pushStack( winnow(this, selector, false), "not", selector);
+       },
 
-       if ( !checkSet ) {
-               checkSet = set;
-       }
+       filter: function( selector ) {
+               return this.pushStack( winnow(this, selector, true), "filter", selector );
+       },
 
-       if ( !checkSet ) {
-               Sizzle.error( cur || selector );
-       }
+       is: function( selector ) {
+               return !!selector && (
+                       typeof selector === "string" ?
+                               // If this is a positional/relative selector, check membership in the returned set
+                               // so $("p:first").is("p:last") won't return true for a doc with two "p".
+                               rneedsContext.test( selector ) ?
+                                       jQuery( selector, this.context ).index( this[0] ) >= 0 :
+                                       jQuery.filter( selector, this ).length > 0 :
+                               this.filter( selector ).length > 0 );
+       },
 
-       if ( toString.call(checkSet) === "[object Array]" ) {
-               if ( !prune ) {
-                       results.push.apply( results, checkSet );
+       closest: function( selectors, context ) {
+               var cur,
+                       i = 0,
+                       l = this.length,
+                       ret = [],
+                       pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+                               jQuery( selectors, context || this.context ) :
+                               0;
 
-               } else if ( context && context.nodeType === 1 ) {
-                       for ( i = 0; checkSet[i] != null; i++ ) {
-                               if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
-                                       results.push( set[i] );
-                               }
-                       }
+               for ( ; i < l; i++ ) {
+                       cur = this[i];
 
-               } else {
-                       for ( i = 0; checkSet[i] != null; i++ ) {
-                               if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
-                                       results.push( set[i] );
+                       while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
+                               if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+                                       ret.push( cur );
+                                       break;
                                }
+                               cur = cur.parentNode;
                        }
                }
 
-       } else {
-               makeArray( checkSet, results );
-       }
+               ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
 
-       if ( extra ) {
-               Sizzle( extra, origContext, results, seed );
-               Sizzle.uniqueSort( results );
-       }
+               return this.pushStack( ret, "closest", selectors );
+       },
 
-       return results;
-};
+       // Determine the position of an element within
+       // the matched set of elements
+       index: function( elem ) {
 
-Sizzle.uniqueSort = function( results ) {
-       if ( sortOrder ) {
-               hasDuplicate = baseHasDuplicate;
-               results.sort( sortOrder );
+               // No argument, return index in parent
+               if ( !elem ) {
+                       return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+               }
 
-               if ( hasDuplicate ) {
-                       for ( var i = 1; i < results.length; i++ ) {
-                               if ( results[i] === results[ i - 1 ] ) {
-                                       results.splice( i--, 1 );
-                               }
-                       }
-               }
-       }
-
-       return results;
-};
-
-Sizzle.matches = function( expr, set ) {
-       return Sizzle( expr, null, null, set );
-};
-
-Sizzle.matchesSelector = function( node, expr ) {
-       return Sizzle( expr, null, null, [node] ).length > 0;
-};
-
-Sizzle.find = function( expr, context, isXML ) {
-       var set, i, len, match, type, left;
-
-       if ( !expr ) {
-               return [];
-       }
-
-       for ( i = 0, len = Expr.order.length; i < len; i++ ) {
-               type = Expr.order[i];
-
-               if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
-                       left = match[1];
-                       match.splice( 1, 1 );
-
-                       if ( left.substr( left.length - 1 ) !== "\\" ) {
-                               match[1] = (match[1] || "").replace( rBackslash, "" );
-                               set = Expr.find[ type ]( match, context, isXML );
-
-                               if ( set != null ) {
-                                       expr = expr.replace( Expr.match[ type ], "" );
-                                       break;
-                               }
-                       }
-               }
-       }
-
-       if ( !set ) {
-               set = typeof context.getElementsByTagName !== "undefined" ?
-                       context.getElementsByTagName( "*" ) :
-                       [];
-       }
-
-       return { set: set, expr: expr };
-};
-
-Sizzle.filter = function( expr, set, inplace, not ) {
-       var match, anyFound,
-               type, found, item, filter, left,
-               i, pass,
-               old = expr,
-               result = [],
-               curLoop = set,
-               isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
-
-       while ( expr && set.length ) {
-               for ( type in Expr.filter ) {
-                       if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
-                               filter = Expr.filter[ type ];
-                               left = match[1];
-
-                               anyFound = false;
-
-                               match.splice(1,1);
-
-                               if ( left.substr( left.length - 1 ) === "\\" ) {
-                                       continue;
-                               }
-
-                               if ( curLoop === result ) {
-                                       result = [];
-                               }
-
-                               if ( Expr.preFilter[ type ] ) {
-                                       match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
-
-                                       if ( !match ) {
-                                               anyFound = found = true;
-
-                                       } else if ( match === true ) {
-                                               continue;
-                                       }
-                               }
-
-                               if ( match ) {
-                                       for ( i = 0; (item = curLoop[i]) != null; i++ ) {
-                                               if ( item ) {
-                                                       found = filter( item, match, i, curLoop );
-                                                       pass = not ^ found;
-
-                                                       if ( inplace && found != null ) {
-                                                               if ( pass ) {
-                                                                       anyFound = true;
-
-                                                               } else {
-                                                                       curLoop[i] = false;
-                                                               }
-
-                                                       } else if ( pass ) {
-                                                               result.push( item );
-                                                               anyFound = true;
-                                                       }
-                                               }
-                                       }
-                               }
-
-                               if ( found !== undefined ) {
-                                       if ( !inplace ) {
-                                               curLoop = result;
-                                       }
-
-                                       expr = expr.replace( Expr.match[ type ], "" );
-
-                                       if ( !anyFound ) {
-                                               return [];
-                                       }
-
-                                       break;
-                               }
-                       }
-               }
-
-               // Improper expression
-               if ( expr === old ) {
-                       if ( anyFound == null ) {
-                               Sizzle.error( expr );
-
-                       } else {
-                               break;
-                       }
-               }
-
-               old = expr;
-       }
-
-       return curLoop;
-};
-
-Sizzle.error = function( msg ) {
-       throw new Error( "Syntax error, unrecognized expression: " + msg );
-};
-
-/**
- * Utility function for retreiving the text value of an array of DOM nodes
- * @param {Array|Element} elem
- */
-var getText = Sizzle.getText = function( elem ) {
-    var i, node,
-               nodeType = elem.nodeType,
-               ret = "";
-
-       if ( nodeType ) {
-               if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
-                       // Use textContent || innerText for elements
-                       if ( typeof elem.textContent === 'string' ) {
-                               return elem.textContent;
-                       } else if ( typeof elem.innerText === 'string' ) {
-                               // Replace IE's carriage returns
-                               return elem.innerText.replace( rReturn, '' );
-                       } else {
-                               // Traverse it's children
-                               for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
-                                       ret += getText( elem );
-                               }
-                       }
-               } else if ( nodeType === 3 || nodeType === 4 ) {
-                       return elem.nodeValue;
-               }
-       } else {
-
-               // If no nodeType, this is expected to be an array
-               for ( i = 0; (node = elem[i]); i++ ) {
-                       // Do not traverse comment nodes
-                       if ( node.nodeType !== 8 ) {
-                               ret += getText( node );
-                       }
-               }
-       }
-       return ret;
-};
-
-var Expr = Sizzle.selectors = {
-       order: [ "ID", "NAME", "TAG" ],
-
-       match: {
-               ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
-               CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
-               NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
-               ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
-               TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
-               CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
-               POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
-               PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
-       },
-
-       leftMatch: {},
-
-       attrMap: {
-               "class": "className",
-               "for": "htmlFor"
-       },
-
-       attrHandle: {
-               href: function( elem ) {
-                       return elem.getAttribute( "href" );
-               },
-               type: function( elem ) {
-                       return elem.getAttribute( "type" );
-               }
-       },
-
-       relative: {
-               "+": function(checkSet, part){
-                       var isPartStr = typeof part === "string",
-                               isTag = isPartStr && !rNonWord.test( part ),
-                               isPartStrNotTag = isPartStr && !isTag;
-
-                       if ( isTag ) {
-                               part = part.toLowerCase();
-                       }
-
-                       for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
-                               if ( (elem = checkSet[i]) ) {
-                                       while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
-
-                                       checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
-                                               elem || false :
-                                               elem === part;
-                               }
-                       }
-
-                       if ( isPartStrNotTag ) {
-                               Sizzle.filter( part, checkSet, true );
-                       }
-               },
-
-               ">": function( checkSet, part ) {
-                       var elem,
-                               isPartStr = typeof part === "string",
-                               i = 0,
-                               l = checkSet.length;
-
-                       if ( isPartStr && !rNonWord.test( part ) ) {
-                               part = part.toLowerCase();
-
-                               for ( ; i < l; i++ ) {
-                                       elem = checkSet[i];
-
-                                       if ( elem ) {
-                                               var parent = elem.parentNode;
-                                               checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
-                                       }
-                               }
-
-                       } else {
-                               for ( ; i < l; i++ ) {
-                                       elem = checkSet[i];
-
-                                       if ( elem ) {
-                                               checkSet[i] = isPartStr ?
-                                                       elem.parentNode :
-                                                       elem.parentNode === part;
-                                       }
-                               }
-
-                               if ( isPartStr ) {
-                                       Sizzle.filter( part, checkSet, true );
-                               }
-                       }
-               },
-
-               "": function(checkSet, part, isXML){
-                       var nodeCheck,
-                               doneName = done++,
-                               checkFn = dirCheck;
-
-                       if ( typeof part === "string" && !rNonWord.test( part ) ) {
-                               part = part.toLowerCase();
-                               nodeCheck = part;
-                               checkFn = dirNodeCheck;
-                       }
-
-                       checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
-               },
-
-               "~": function( checkSet, part, isXML ) {
-                       var nodeCheck,
-                               doneName = done++,
-                               checkFn = dirCheck;
-
-                       if ( typeof part === "string" && !rNonWord.test( part ) ) {
-                               part = part.toLowerCase();
-                               nodeCheck = part;
-                               checkFn = dirNodeCheck;
-                       }
-
-                       checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
-               }
-       },
-
-       find: {
-               ID: function( match, context, isXML ) {
-                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
-                               var m = context.getElementById(match[1]);
-                               // Check parentNode to catch when Blackberry 4.6 returns
-                               // nodes that are no longer in the document #6963
-                               return m && m.parentNode ? [m] : [];
-                       }
-               },
-
-               NAME: function( match, context ) {
-                       if ( typeof context.getElementsByName !== "undefined" ) {
-                               var ret = [],
-                                       results = context.getElementsByName( match[1] );
-
-                               for ( var i = 0, l = results.length; i < l; i++ ) {
-                                       if ( results[i].getAttribute("name") === match[1] ) {
-                                               ret.push( results[i] );
-                                       }
-                               }
-
-                               return ret.length === 0 ? null : ret;
-                       }
-               },
-
-               TAG: function( match, context ) {
-                       if ( typeof context.getElementsByTagName !== "undefined" ) {
-                               return context.getElementsByTagName( match[1] );
-                       }
-               }
-       },
-       preFilter: {
-               CLASS: function( match, curLoop, inplace, result, not, isXML ) {
-                       match = " " + match[1].replace( rBackslash, "" ) + " ";
-
-                       if ( isXML ) {
-                               return match;
-                       }
-
-                       for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
-                               if ( elem ) {
-                                       if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
-                                               if ( !inplace ) {
-                                                       result.push( elem );
-                                               }
-
-                                       } else if ( inplace ) {
-                                               curLoop[i] = false;
-                                       }
-                               }
-                       }
-
-                       return false;
-               },
-
-               ID: function( match ) {
-                       return match[1].replace( rBackslash, "" );
-               },
-
-               TAG: function( match, curLoop ) {
-                       return match[1].replace( rBackslash, "" ).toLowerCase();
-               },
-
-               CHILD: function( match ) {
-                       if ( match[1] === "nth" ) {
-                               if ( !match[2] ) {
-                                       Sizzle.error( match[0] );
-                               }
-
-                               match[2] = match[2].replace(/^\+|\s*/g, '');
-
-                               // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
-                               var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
-                                       match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
-                                       !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
-
-                               // calculate the numbers (first)n+(last) including if they are negative
-                               match[2] = (test[1] + (test[2] || 1)) - 0;
-                               match[3] = test[3] - 0;
-                       }
-                       else if ( match[2] ) {
-                               Sizzle.error( match[0] );
-                       }
-
-                       // TODO: Move to normal caching system
-                       match[0] = done++;
-
-                       return match;
-               },
-
-               ATTR: function( match, curLoop, inplace, result, not, isXML ) {
-                       var name = match[1] = match[1].replace( rBackslash, "" );
-
-                       if ( !isXML && Expr.attrMap[name] ) {
-                               match[1] = Expr.attrMap[name];
-                       }
-
-                       // Handle if an un-quoted value was used
-                       match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
-
-                       if ( match[2] === "~=" ) {
-                               match[4] = " " + match[4] + " ";
-                       }
-
-                       return match;
-               },
-
-               PSEUDO: function( match, curLoop, inplace, result, not ) {
-                       if ( match[1] === "not" ) {
-                               // If we're dealing with a complex expression, or a simple one
-                               if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
-                                       match[3] = Sizzle(match[3], null, null, curLoop);
-
-                               } else {
-                                       var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
-
-                                       if ( !inplace ) {
-                                               result.push.apply( result, ret );
-                                       }
-
-                                       return false;
-                               }
-
-                       } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
-                               return true;
-                       }
-
-                       return match;
-               },
-
-               POS: function( match ) {
-                       match.unshift( true );
-
-                       return match;
-               }
-       },
-
-       filters: {
-               enabled: function( elem ) {
-                       return elem.disabled === false && elem.type !== "hidden";
-               },
-
-               disabled: function( elem ) {
-                       return elem.disabled === true;
-               },
-
-               checked: function( elem ) {
-                       return elem.checked === true;
-               },
-
-               selected: function( elem ) {
-                       // Accessing this property makes selected-by-default
-                       // options in Safari work properly
-                       if ( elem.parentNode ) {
-                               elem.parentNode.selectedIndex;
-                       }
-
-                       return elem.selected === true;
-               },
-
-               parent: function( elem ) {
-                       return !!elem.firstChild;
-               },
-
-               empty: function( elem ) {
-                       return !elem.firstChild;
-               },
-
-               has: function( elem, i, match ) {
-                       return !!Sizzle( match[3], elem ).length;
-               },
-
-               header: function( elem ) {
-                       return (/h\d/i).test( elem.nodeName );
-               },
-
-               text: function( elem ) {
-                       var attr = elem.getAttribute( "type" ), type = elem.type;
-                       // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
-                       // use getAttribute instead to test this case
-                       return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
-               },
-
-               radio: function( elem ) {
-                       return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
-               },
-
-               checkbox: function( elem ) {
-                       return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
-               },
-
-               file: function( elem ) {
-                       return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
-               },
-
-               password: function( elem ) {
-                       return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
-               },
-
-               submit: function( elem ) {
-                       var name = elem.nodeName.toLowerCase();
-                       return (name === "input" || name === "button") && "submit" === elem.type;
-               },
-
-               image: function( elem ) {
-                       return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
-               },
-
-               reset: function( elem ) {
-                       var name = elem.nodeName.toLowerCase();
-                       return (name === "input" || name === "button") && "reset" === elem.type;
-               },
-
-               button: function( elem ) {
-                       var name = elem.nodeName.toLowerCase();
-                       return name === "input" && "button" === elem.type || name === "button";
-               },
-
-               input: function( elem ) {
-                       return (/input|select|textarea|button/i).test( elem.nodeName );
-               },
-
-               focus: function( elem ) {
-                       return elem === elem.ownerDocument.activeElement;
-               }
-       },
-       setFilters: {
-               first: function( elem, i ) {
-                       return i === 0;
-               },
-
-               last: function( elem, i, match, array ) {
-                       return i === array.length - 1;
-               },
-
-               even: function( elem, i ) {
-                       return i % 2 === 0;
-               },
-
-               odd: function( elem, i ) {
-                       return i % 2 === 1;
-               },
-
-               lt: function( elem, i, match ) {
-                       return i < match[3] - 0;
-               },
-
-               gt: function( elem, i, match ) {
-                       return i > match[3] - 0;
-               },
-
-               nth: function( elem, i, match ) {
-                       return match[3] - 0 === i;
-               },
-
-               eq: function( elem, i, match ) {
-                       return match[3] - 0 === i;
-               }
-       },
-       filter: {
-               PSEUDO: function( elem, match, i, array ) {
-                       var name = match[1],
-                               filter = Expr.filters[ name ];
-
-                       if ( filter ) {
-                               return filter( elem, i, match, array );
-
-                       } else if ( name === "contains" ) {
-                               return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
-
-                       } else if ( name === "not" ) {
-                               var not = match[3];
-
-                               for ( var j = 0, l = not.length; j < l; j++ ) {
-                                       if ( not[j] === elem ) {
-                                               return false;
-                                       }
-                               }
-
-                               return true;
-
-                       } else {
-                               Sizzle.error( name );
-                       }
-               },
-
-               CHILD: function( elem, match ) {
-                       var first, last,
-                               doneName, parent, cache,
-                               count, diff,
-                               type = match[1],
-                               node = elem;
-
-                       switch ( type ) {
-                               case "only":
-                               case "first":
-                                       while ( (node = node.previousSibling) ) {
-                                               if ( node.nodeType === 1 ) {
-                                                       return false;
-                                               }
-                                       }
-
-                                       if ( type === "first" ) {
-                                               return true;
-                                       }
-
-                                       node = elem;
-
-                                       /* falls through */
-                               case "last":
-                                       while ( (node = node.nextSibling) ) {
-                                               if ( node.nodeType === 1 ) {
-                                                       return false;
-                                               }
-                                       }
-
-                                       return true;
-
-                               case "nth":
-                                       first = match[2];
-                                       last = match[3];
-
-                                       if ( first === 1 && last === 0 ) {
-                                               return true;
-                                       }
-
-                                       doneName = match[0];
-                                       parent = elem.parentNode;
-
-                                       if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
-                                               count = 0;
-
-                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {
-                                                       if ( node.nodeType === 1 ) {
-                                                               node.nodeIndex = ++count;
-                                                       }
-                                               }
-
-                                               parent[ expando ] = doneName;
-                                       }
-
-                                       diff = elem.nodeIndex - last;
-
-                                       if ( first === 0 ) {
-                                               return diff === 0;
-
-                                       } else {
-                                               return ( diff % first === 0 && diff / first >= 0 );
-                                       }
-                       }
-               },
-
-               ID: function( elem, match ) {
-                       return elem.nodeType === 1 && elem.getAttribute("id") === match;
-               },
-
-               TAG: function( elem, match ) {
-                       return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
-               },
-
-               CLASS: function( elem, match ) {
-                       return (" " + (elem.className || elem.getAttribute("class")) + " ")
-                               .indexOf( match ) > -1;
-               },
-
-               ATTR: function( elem, match ) {
-                       var name = match[1],
-                               result = Sizzle.attr ?
-                                       Sizzle.attr( elem, name ) :
-                                       Expr.attrHandle[ name ] ?
-                                       Expr.attrHandle[ name ]( elem ) :
-                                       elem[ name ] != null ?
-                                               elem[ name ] :
-                                               elem.getAttribute( name ),
-                               value = result + "",
-                               type = match[2],
-                               check = match[4];
-
-                       return result == null ?
-                               type === "!=" :
-                               !type && Sizzle.attr ?
-                               result != null :
-                               type === "=" ?
-                               value === check :
-                               type === "*=" ?
-                               value.indexOf(check) >= 0 :
-                               type === "~=" ?
-                               (" " + value + " ").indexOf(check) >= 0 :
-                               !check ?
-                               value && result !== false :
-                               type === "!=" ?
-                               value !== check :
-                               type === "^=" ?
-                               value.indexOf(check) === 0 :
-                               type === "$=" ?
-                               value.substr(value.length - check.length) === check :
-                               type === "|=" ?
-                               value === check || value.substr(0, check.length + 1) === check + "-" :
-                               false;
-               },
-
-               POS: function( elem, match, i, array ) {
-                       var name = match[2],
-                               filter = Expr.setFilters[ name ];
-
-                       if ( filter ) {
-                               return filter( elem, i, match, array );
-                       }
-               }
-       }
-};
-
-var origPOS = Expr.match.POS,
-       fescape = function(all, num){
-               return "\\" + (num - 0 + 1);
-       };
-
-for ( var type in Expr.match ) {
-       Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
-       Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
-}
-// Expose origPOS
-// "global" as in regardless of relation to brackets/parens
-Expr.match.globalPOS = origPOS;
-
-var makeArray = function( array, results ) {
-       array = Array.prototype.slice.call( array, 0 );
-
-       if ( results ) {
-               results.push.apply( results, array );
-               return results;
-       }
-
-       return array;
-};
-
-// Perform a simple check to determine if the browser is capable of
-// converting a NodeList to an array using builtin methods.
-// Also verifies that the returned array holds DOM nodes
-// (which is not the case in the Blackberry browser)
-try {
-       Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
-
-// Provide a fallback method if it does not work
-} catch( e ) {
-       makeArray = function( array, results ) {
-               var i = 0,
-                       ret = results || [];
-
-               if ( toString.call(array) === "[object Array]" ) {
-                       Array.prototype.push.apply( ret, array );
-
-               } else {
-                       if ( typeof array.length === "number" ) {
-                               for ( var l = array.length; i < l; i++ ) {
-                                       ret.push( array[i] );
-                               }
-
-                       } else {
-                               for ( ; array[i]; i++ ) {
-                                       ret.push( array[i] );
-                               }
-                       }
-               }
-
-               return ret;
-       };
-}
-
-var sortOrder, siblingCheck;
-
-if ( document.documentElement.compareDocumentPosition ) {
-       sortOrder = function( a, b ) {
-               if ( a === b ) {
-                       hasDuplicate = true;
-                       return 0;
-               }
-
-               if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
-                       return a.compareDocumentPosition ? -1 : 1;
-               }
-
-               return a.compareDocumentPosition(b) & 4 ? -1 : 1;
-       };
-
-} else {
-       sortOrder = function( a, b ) {
-               // The nodes are identical, we can exit early
-               if ( a === b ) {
-                       hasDuplicate = true;
-                       return 0;
-
-               // Fallback to using sourceIndex (in IE) if it's available on both nodes
-               } else if ( a.sourceIndex && b.sourceIndex ) {
-                       return a.sourceIndex - b.sourceIndex;
-               }
-
-               var al, bl,
-                       ap = [],
-                       bp = [],
-                       aup = a.parentNode,
-                       bup = b.parentNode,
-                       cur = aup;
-
-               // If the nodes are siblings (or identical) we can do a quick check
-               if ( aup === bup ) {
-                       return siblingCheck( a, b );
-
-               // If no parents were found then the nodes are disconnected
-               } else if ( !aup ) {
-                       return -1;
-
-               } else if ( !bup ) {
-                       return 1;
-               }
-
-               // Otherwise they're somewhere else in the tree so we need
-               // to build up a full list of the parentNodes for comparison
-               while ( cur ) {
-                       ap.unshift( cur );
-                       cur = cur.parentNode;
-               }
-
-               cur = bup;
-
-               while ( cur ) {
-                       bp.unshift( cur );
-                       cur = cur.parentNode;
-               }
-
-               al = ap.length;
-               bl = bp.length;
-
-               // Start walking down the tree looking for a discrepancy
-               for ( var i = 0; i < al && i < bl; i++ ) {
-                       if ( ap[i] !== bp[i] ) {
-                               return siblingCheck( ap[i], bp[i] );
-                       }
-               }
-
-               // We ended someplace up the tree so do a sibling check
-               return i === al ?
-                       siblingCheck( a, bp[i], -1 ) :
-                       siblingCheck( ap[i], b, 1 );
-       };
-
-       siblingCheck = function( a, b, ret ) {
-               if ( a === b ) {
-                       return ret;
-               }
-
-               var cur = a.nextSibling;
-
-               while ( cur ) {
-                       if ( cur === b ) {
-                               return -1;
-                       }
-
-                       cur = cur.nextSibling;
-               }
-
-               return 1;
-       };
-}
-
-// Check to see if the browser returns elements by name when
-// querying by getElementById (and provide a workaround)
-(function(){
-       // We're going to inject a fake input element with a specified name
-       var form = document.createElement("div"),
-               id = "script" + (new Date()).getTime(),
-               root = document.documentElement;
-
-       form.innerHTML = "<a name='" + id + "'/>";
-
-       // Inject it into the root element, check its status, and remove it quickly
-       root.insertBefore( form, root.firstChild );
-
-       // The workaround has to do additional checks after a getElementById
-       // Which slows things down for other browsers (hence the branching)
-       if ( document.getElementById( id ) ) {
-               Expr.find.ID = function( match, context, isXML ) {
-                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
-                               var m = context.getElementById(match[1]);
-
-                               return m ?
-                                       m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
-                                               [m] :
-                                               undefined :
-                                       [];
-                       }
-               };
-
-               Expr.filter.ID = function( elem, match ) {
-                       var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
-
-                       return elem.nodeType === 1 && node && node.nodeValue === match;
-               };
-       }
-
-       root.removeChild( form );
-
-       // release memory in IE
-       root = form = null;
-})();
-
-(function(){
-       // Check to see if the browser returns only elements
-       // when doing getElementsByTagName("*")
-
-       // Create a fake element
-       var div = document.createElement("div");
-       div.appendChild( document.createComment("") );
-
-       // Make sure no comments are found
-       if ( div.getElementsByTagName("*").length > 0 ) {
-               Expr.find.TAG = function( match, context ) {
-                       var results = context.getElementsByTagName( match[1] );
-
-                       // Filter out possible comments
-                       if ( match[1] === "*" ) {
-                               var tmp = [];
-
-                               for ( var i = 0; results[i]; i++ ) {
-                                       if ( results[i].nodeType === 1 ) {
-                                               tmp.push( results[i] );
-                                       }
-                               }
-
-                               results = tmp;
-                       }
-
-                       return results;
-               };
-       }
-
-       // Check to see if an attribute returns normalized href attributes
-       div.innerHTML = "<a href='#'></a>";
-
-       if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
-                       div.firstChild.getAttribute("href") !== "#" ) {
-
-               Expr.attrHandle.href = function( elem ) {
-                       return elem.getAttribute( "href", 2 );
-               };
-       }
-
-       // release memory in IE
-       div = null;
-})();
-
-if ( document.querySelectorAll ) {
-       (function(){
-               var oldSizzle = Sizzle,
-                       div = document.createElement("div"),
-                       id = "__sizzle__";
-
-               div.innerHTML = "<p class='TEST'></p>";
-
-               // Safari can't handle uppercase or unicode characters when
-               // in quirks mode.
-               if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
-                       return;
-               }
-
-               Sizzle = function( query, context, extra, seed ) {
-                       context = context || document;
-
-                       // Only use querySelectorAll on non-XML documents
-                       // (ID selectors don't work in non-HTML documents)
-                       if ( !seed && !Sizzle.isXML(context) ) {
-                               // See if we find a selector to speed up
-                               var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
-
-                               if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
-                                       // Speed-up: Sizzle("TAG")
-                                       if ( match[1] ) {
-                                               return makeArray( context.getElementsByTagName( query ), extra );
-
-                                       // Speed-up: Sizzle(".CLASS")
-                                       } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
-                                               return makeArray( context.getElementsByClassName( match[2] ), extra );
-                                       }
-                               }
-
-                               if ( context.nodeType === 9 ) {
-                                       // Speed-up: Sizzle("body")
-                                       // The body element only exists once, optimize finding it
-                                       if ( query === "body" && context.body ) {
-                                               return makeArray( [ context.body ], extra );
-
-                                       // Speed-up: Sizzle("#ID")
-                                       } else if ( match && match[3] ) {
-                                               var elem = context.getElementById( match[3] );
-
-                                               // Check parentNode to catch when Blackberry 4.6 returns
-                                               // nodes that are no longer in the document #6963
-                                               if ( elem && elem.parentNode ) {
-                                                       // Handle the case where IE and Opera return items
-                                                       // by name instead of ID
-                                                       if ( elem.id === match[3] ) {
-                                                               return makeArray( [ elem ], extra );
-                                                       }
-
-                                               } else {
-                                                       return makeArray( [], extra );
-                                               }
-                                       }
-
-                                       try {
-                                               return makeArray( context.querySelectorAll(query), extra );
-                                       } catch(qsaError) {}
-
-                               // qSA works strangely on Element-rooted queries
-                               // We can work around this by specifying an extra ID on the root
-                               // and working up from there (Thanks to Andrew Dupont for the technique)
-                               // IE 8 doesn't work on object elements
-                               } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
-                                       var oldContext = context,
-                                               old = context.getAttribute( "id" ),
-                                               nid = old || id,
-                                               hasParent = context.parentNode,
-                                               relativeHierarchySelector = /^\s*[+~]/.test( query );
-
-                                       if ( !old ) {
-                                               context.setAttribute( "id", nid );
-                                       } else {
-                                               nid = nid.replace( /'/g, "\\$&" );
-                                       }
-                                       if ( relativeHierarchySelector && hasParent ) {
-                                               context = context.parentNode;
-                                       }
-
-                                       try {
-                                               if ( !relativeHierarchySelector || hasParent ) {
-                                                       return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
-                                               }
-
-                                       } catch(pseudoError) {
-                                       } finally {
-                                               if ( !old ) {
-                                                       oldContext.removeAttribute( "id" );
-                                               }
-                                       }
-                               }
-                       }
-
-                       return oldSizzle(query, context, extra, seed);
-               };
-
-               for ( var prop in oldSizzle ) {
-                       Sizzle[ prop ] = oldSizzle[ prop ];
-               }
-
-               // release memory in IE
-               div = null;
-       })();
-}
-
-(function(){
-       var html = document.documentElement,
-               matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
-
-       if ( matches ) {
-               // Check to see if it's possible to do matchesSelector
-               // on a disconnected node (IE 9 fails this)
-               var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
-                       pseudoWorks = false;
-
-               try {
-                       // This should fail with an exception
-                       // Gecko does not error, returns false instead
-                       matches.call( document.documentElement, "[test!='']:sizzle" );
-
-               } catch( pseudoError ) {
-                       pseudoWorks = true;
-               }
-
-               Sizzle.matchesSelector = function( node, expr ) {
-                       // Make sure that attribute selectors are quoted
-                       expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
-
-                       if ( !Sizzle.isXML( node ) ) {
-                               try {
-                                       if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
-                                               var ret = matches.call( node, expr );
-
-                                               // IE 9's matchesSelector returns false on disconnected nodes
-                                               if ( ret || !disconnectedMatch ||
-                                                               // As well, disconnected nodes are said to be in a document
-                                                               // fragment in IE 9, so check for that
-                                                               node.document && node.document.nodeType !== 11 ) {
-                                                       return ret;
-                                               }
-                                       }
-                               } catch(e) {}
-                       }
-
-                       return Sizzle(expr, null, null, [node]).length > 0;
-               };
-       }
-})();
-
-(function(){
-       var div = document.createElement("div");
-
-       div.innerHTML = "<div class='test e'></div><div class='test'></div>";
-
-       // Opera can't find a second classname (in 9.6)
-       // Also, make sure that getElementsByClassName actually exists
-       if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
-               return;
-       }
-
-       // Safari caches class attributes, doesn't catch changes (in 3.2)
-       div.lastChild.className = "e";
-
-       if ( div.getElementsByClassName("e").length === 1 ) {
-               return;
-       }
-
-       Expr.order.splice(1, 0, "CLASS");
-       Expr.find.CLASS = function( match, context, isXML ) {
-               if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
-                       return context.getElementsByClassName(match[1]);
-               }
-       };
-
-       // release memory in IE
-       div = null;
-})();
-
-function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
-       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
-               var elem = checkSet[i];
-
-               if ( elem ) {
-                       var match = false;
-
-                       elem = elem[dir];
-
-                       while ( elem ) {
-                               if ( elem[ expando ] === doneName ) {
-                                       match = checkSet[elem.sizset];
-                                       break;
-                               }
-
-                               if ( elem.nodeType === 1 && !isXML ){
-                                       elem[ expando ] = doneName;
-                                       elem.sizset = i;
-                               }
-
-                               if ( elem.nodeName.toLowerCase() === cur ) {
-                                       match = elem;
-                                       break;
-                               }
-
-                               elem = elem[dir];
-                       }
-
-                       checkSet[i] = match;
-               }
-       }
-}
-
-function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
-       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
-               var elem = checkSet[i];
-
-               if ( elem ) {
-                       var match = false;
-
-                       elem = elem[dir];
-
-                       while ( elem ) {
-                               if ( elem[ expando ] === doneName ) {
-                                       match = checkSet[elem.sizset];
-                                       break;
-                               }
-
-                               if ( elem.nodeType === 1 ) {
-                                       if ( !isXML ) {
-                                               elem[ expando ] = doneName;
-                                               elem.sizset = i;
-                                       }
-
-                                       if ( typeof cur !== "string" ) {
-                                               if ( elem === cur ) {
-                                                       match = true;
-                                                       break;
-                                               }
-
-                                       } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
-                                               match = elem;
-                                               break;
-                                       }
-                               }
-
-                               elem = elem[dir];
-                       }
-
-                       checkSet[i] = match;
-               }
-       }
-}
-
-if ( document.documentElement.contains ) {
-       Sizzle.contains = function( a, b ) {
-               return a !== b && (a.contains ? a.contains(b) : true);
-       };
-
-} else if ( document.documentElement.compareDocumentPosition ) {
-       Sizzle.contains = function( a, b ) {
-               return !!(a.compareDocumentPosition(b) & 16);
-       };
-
-} else {
-       Sizzle.contains = function() {
-               return false;
-       };
-}
-
-Sizzle.isXML = function( elem ) {
-       // documentElement is verified for cases where it doesn't yet exist
-       // (such as loading iframes in IE - #4833)
-       var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
-
-       return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
-
-var posProcess = function( selector, context, seed ) {
-       var match,
-               tmpSet = [],
-               later = "",
-               root = context.nodeType ? [context] : context;
-
-       // Position selectors must be done after the filter
-       // And so must :not(positional) so we move all PSEUDOs to the end
-       while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
-               later += match[0];
-               selector = selector.replace( Expr.match.PSEUDO, "" );
-       }
-
-       selector = Expr.relative[selector] ? selector + "*" : selector;
-
-       for ( var i = 0, l = root.length; i < l; i++ ) {
-               Sizzle( selector, root[i], tmpSet, seed );
-       }
-
-       return Sizzle.filter( later, tmpSet );
-};
-
-// EXPOSE
-// Override sizzle attribute retrieval
-Sizzle.attr = jQuery.attr;
-Sizzle.selectors.attrMap = {};
-jQuery.find = Sizzle;
-jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.filters;
-jQuery.unique = Sizzle.uniqueSort;
-jQuery.text = Sizzle.getText;
-jQuery.isXMLDoc = Sizzle.isXML;
-jQuery.contains = Sizzle.contains;
-
-
-})();
-
-
-var runtil = /Until$/,
-       rparentsprev = /^(?:parents|prevUntil|prevAll)/,
-       // Note: This RegExp should be improved, or likely pulled from Sizzle
-       rmultiselector = /,/,
-       isSimple = /^.[^:#\[\.,]*$/,
-       slice = Array.prototype.slice,
-       POS = jQuery.expr.match.globalPOS,
-       // methods guaranteed to produce a unique set when starting from a unique set
-       guaranteedUnique = {
-               children: true,
-               contents: true,
-               next: true,
-               prev: true
-       };
-
-jQuery.fn.extend({
-       find: function( selector ) {
-               var self = this,
-                       i, l;
-
-               if ( typeof selector !== "string" ) {
-                       return jQuery( selector ).filter(function() {
-                               for ( i = 0, l = self.length; i < l; i++ ) {
-                                       if ( jQuery.contains( self[ i ], this ) ) {
-                                               return true;
-                                       }
-                               }
-                       });
-               }
-
-               var ret = this.pushStack( "", "find", selector ),
-                       length, n, r;
-
-               for ( i = 0, l = this.length; i < l; i++ ) {
-                       length = ret.length;
-                       jQuery.find( selector, this[i], ret );
-
-                       if ( i > 0 ) {
-                               // Make sure that the results are unique
-                               for ( n = length; n < ret.length; n++ ) {
-                                       for ( r = 0; r < length; r++ ) {
-                                               if ( ret[r] === ret[n] ) {
-                                                       ret.splice(n--, 1);
-                                                       break;
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               return ret;
-       },
-
-       has: function( target ) {
-               var targets = jQuery( target );
-               return this.filter(function() {
-                       for ( var i = 0, l = targets.length; i < l; i++ ) {
-                               if ( jQuery.contains( this, targets[i] ) ) {
-                                       return true;
-                               }
-                       }
-               });
-       },
-
-       not: function( selector ) {
-               return this.pushStack( winnow(this, selector, false), "not", selector);
-       },
-
-       filter: function( selector ) {
-               return this.pushStack( winnow(this, selector, true), "filter", selector );
-       },
-
-       is: function( selector ) {
-               return !!selector && (
-                       typeof selector === "string" ?
-                               // If this is a positional selector, check membership in the returned set
-                               // so $("p:first").is("p:last") won't return true for a doc with two "p".
-                               POS.test( selector ) ?
-                                       jQuery( selector, this.context ).index( this[0] ) >= 0 :
-                                       jQuery.filter( selector, this ).length > 0 :
-                               this.filter( selector ).length > 0 );
-       },
-
-       closest: function( selectors, context ) {
-               var ret = [], i, l, cur = this[0];
-
-               // Array (deprecated as of jQuery 1.7)
-               if ( jQuery.isArray( selectors ) ) {
-                       var level = 1;
-
-                       while ( cur && cur.ownerDocument && cur !== context ) {
-                               for ( i = 0; i < selectors.length; i++ ) {
-
-                                       if ( jQuery( cur ).is( selectors[ i ] ) ) {
-                                               ret.push({ selector: selectors[ i ], elem: cur, level: level });
-                                       }
-                               }
-
-                               cur = cur.parentNode;
-                               level++;
-                       }
-
-                       return ret;
-               }
-
-               // String
-               var pos = POS.test( selectors ) || typeof selectors !== "string" ?
-                               jQuery( selectors, context || this.context ) :
-                               0;
-
-               for ( i = 0, l = this.length; i < l; i++ ) {
-                       cur = this[i];
-
-                       while ( cur ) {
-                               if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
-                                       ret.push( cur );
-                                       break;
-
-                               } else {
-                                       cur = cur.parentNode;
-                                       if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
-                                               break;
-                                       }
-                               }
-                       }
-               }
-
-               ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
-
-               return this.pushStack( ret, "closest", selectors );
-       },
-
-       // Determine the position of an element within
-       // the matched set of elements
-       index: function( elem ) {
-
-               // No argument, return index in parent
-               if ( !elem ) {
-                       return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
-               }
-
-               // index in selector
-               if ( typeof elem === "string" ) {
-                       return jQuery.inArray( this[0], jQuery( elem ) );
+               // index in selector
+               if ( typeof elem === "string" ) {
+                       return jQuery.inArray( this[0], jQuery( elem ) );
                }
 
                // Locate the position of the desired element
@@ -5556,17 +5276,29 @@ jQuery.fn.extend({
                        jQuery.unique( all ) );
        },
 
-       andSelf: function() {
-               return this.add( this.prevObject );
+       addBack: function( selector ) {
+               return this.add( selector == null ?
+                       this.prevObject : this.prevObject.filter(selector)
+               );
        }
 });
 
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
 // A painfully simple check to see if an element is disconnected
 // from a document (should be improved, where feasible).
 function isDisconnected( node ) {
        return !node || !node.parentNode || node.parentNode.nodeType === 11;
 }
 
+function sibling( cur, dir ) {
+       do {
+               cur = cur[ dir ];
+       } while ( cur && cur.nodeType !== 1 );
+
+       return cur;
+}
+
 jQuery.each({
        parent: function( elem ) {
                var parent = elem.parentNode;
@@ -5579,10 +5311,10 @@ jQuery.each({
                return jQuery.dir( elem, "parentNode", until );
        },
        next: function( elem ) {
-               return jQuery.nth( elem, 2, "nextSibling" );
+               return sibling( elem, "nextSibling" );
        },
        prev: function( elem ) {
-               return jQuery.nth( elem, 2, "previousSibling" );
+               return sibling( elem, "previousSibling" );
        },
        nextAll: function( elem ) {
                return jQuery.dir( elem, "nextSibling" );
@@ -5605,7 +5337,7 @@ jQuery.each({
        contents: function( elem ) {
                return jQuery.nodeName( elem, "iframe" ) ?
                        elem.contentDocument || elem.contentWindow.document :
-                       jQuery.makeArray( elem.childNodes );
+                       jQuery.merge( [], elem.childNodes );
        }
 }, function( name, fn ) {
        jQuery.fn[ name ] = function( until, selector ) {
@@ -5621,11 +5353,11 @@ jQuery.each({
 
                ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
 
-               if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+               if ( this.length > 1 && rparentsprev.test( name ) ) {
                        ret = ret.reverse();
                }
 
-               return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+               return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
        };
 });
 
@@ -5653,19 +5385,6 @@ jQuery.extend({
                return matched;
        },
 
-       nth: function( cur, result, dir, elem ) {
-               result = result || 1;
-               var num = 0;
-
-               for ( ; cur; cur = cur[dir] ) {
-                       if ( cur.nodeType === 1 && ++num === result ) {
-                               break;
-                       }
-               }
-
-               return cur;
-       },
-
        sibling: function( n, elem ) {
                var r = [];
 
@@ -5713,10 +5432,6 @@ function winnow( elements, qualifier, keep ) {
                return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
        });
 }
-
-
-
-
 function createSafeFragment( document ) {
        var list = nodeNames.split( "|" ),
        safeFrag = document.createDocumentFragment();
@@ -5733,19 +5448,20 @@ function createSafeFragment( document ) {
 
 var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
                "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
-       rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+       rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
        rleadingWhitespace = /^\s+/,
-       rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+       rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
        rtagName = /<([\w:]+)/,
        rtbody = /<tbody/i,
        rhtml = /<|&#?\w+;/,
-       rnoInnerhtml = /<(?:script|style)/i,
+       rnoInnerhtml = /<(?:script|style|link)/i,
        rnocache = /<(?:script|object|embed|option|style)/i,
        rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+       rcheckableType = /^(?:checkbox|radio)$/,
        // checked="checked" or checked
        rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
        rscriptType = /\/(java|ecma)script/i,
-       rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+       rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,
        wrapMap = {
                option: [ 1, "<select multiple='multiple'>", "</select>" ],
                legend: [ 1, "<fieldset>", "</fieldset>" ],
@@ -5756,15 +5472,17 @@ var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figca
                area: [ 1, "<map>", "</map>" ],
                _default: [ 0, "", "" ]
        },
-       safeFragment = createSafeFragment( document );
+       safeFragment = createSafeFragment( document ),
+       fragmentDiv = safeFragment.appendChild( document.createElement("div") );
 
 wrapMap.optgroup = wrapMap.option;
 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
 wrapMap.th = wrapMap.td;
 
-// IE can't serialize <link> and <script> tags normally
+// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+// unless wrapped in a div with non-breaking characters in front of it.
 if ( !jQuery.support.htmlSerialize ) {
-       wrapMap._default = [ 1, "div<div>", "</div>" ];
+       wrapMap._default = [ 1, "X<div>", "</div>" ];
 }
 
 jQuery.fn.extend({
@@ -5843,7 +5561,7 @@ jQuery.fn.extend({
 
        append: function() {
                return this.domManip(arguments, true, function( elem ) {
-                       if ( this.nodeType === 1 ) {
+                       if ( this.nodeType === 1 || this.nodeType === 11 ) {
                                this.appendChild( elem );
                        }
                });
@@ -5851,39 +5569,44 @@ jQuery.fn.extend({
 
        prepend: function() {
                return this.domManip(arguments, true, function( elem ) {
-                       if ( this.nodeType === 1 ) {
+                       if ( this.nodeType === 1 || this.nodeType === 11 ) {
                                this.insertBefore( elem, this.firstChild );
                        }
                });
        },
 
        before: function() {
-               if ( this[0] && this[0].parentNode ) {
+               if ( !isDisconnected( this[0] ) ) {
                        return this.domManip(arguments, false, function( elem ) {
                                this.parentNode.insertBefore( elem, this );
                        });
-               } else if ( arguments.length ) {
+               }
+
+               if ( arguments.length ) {
                        var set = jQuery.clean( arguments );
-                       set.push.apply( set, this.toArray() );
-                       return this.pushStack( set, "before", arguments );
+                       return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
                }
        },
 
        after: function() {
-               if ( this[0] && this[0].parentNode ) {
+               if ( !isDisconnected( this[0] ) ) {
                        return this.domManip(arguments, false, function( elem ) {
                                this.parentNode.insertBefore( elem, this.nextSibling );
                        });
-               } else if ( arguments.length ) {
-                       var set = this.pushStack( this, "after", arguments );
-                       set.push.apply( set, jQuery.clean(arguments) );
-                       return set;
+               }
+
+               if ( arguments.length ) {
+                       var set = jQuery.clean( arguments );
+                       return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
                }
        },
 
        // keepData is for internal use only--do not document
        remove: function( selector, keepData ) {
-               for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+               var elem,
+                       i = 0;
+
+               for ( ; (elem = this[i]) != null; i++ ) {
                        if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
                                if ( !keepData && elem.nodeType === 1 ) {
                                        jQuery.cleanData( elem.getElementsByTagName("*") );
@@ -5900,7 +5623,10 @@ jQuery.fn.extend({
        },
 
        empty: function() {
-               for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+               var elem,
+                       i = 0;
+
+               for ( ; (elem = this[i]) != null; i++ ) {
                        // Remove element nodes and prevent memory leaks
                        if ( elem.nodeType === 1 ) {
                                jQuery.cleanData( elem.getElementsByTagName("*") );
@@ -5933,11 +5659,12 @@ jQuery.fn.extend({
                        if ( value === undefined ) {
                                return elem.nodeType === 1 ?
                                        elem.innerHTML.replace( rinlinejQuery, "" ) :
-                                       null;
+                                       undefined;
                        }
 
-
+                       // See if we can take a shortcut and just use innerHTML
                        if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+                               ( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&
                                ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
                                !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
 
@@ -5966,7 +5693,7 @@ jQuery.fn.extend({
        },
 
        replaceWith: function( value ) {
-               if ( this[0] && this[0].parentNode ) {
+               if ( !isDisconnected( this[0] ) ) {
                        // Make sure that the elements are removed from the DOM before they are inserted
                        // this can help fix replacing a parent with child elements
                        if ( jQuery.isFunction( value ) ) {
@@ -5992,11 +5719,11 @@ jQuery.fn.extend({
                                        jQuery(parent).append( value );
                                }
                        });
-               } else {
-                       return this.length ?
-                               this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
-                               this;
                }
+
+               return this.length ?
+                       this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+                       this;
        },
 
        detach: function( selector ) {
@@ -6004,78 +5731,78 @@ jQuery.fn.extend({
        },
 
        domManip: function( args, table, callback ) {
-               var results, first, fragment, parent,
+
+               // Flatten any nested arrays
+               args = [].concat.apply( [], args );
+
+               var results, first, fragment, iNoClone,
+                       i = 0,
                        value = args[0],
-                       scripts = [];
+                       scripts = [],
+                       l = this.length;
 
                // We can't cloneNode fragments that contain checked, in WebKit
-               if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+               if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
                        return this.each(function() {
-                               jQuery(this).domManip( args, table, callback, true );
+                               jQuery(this).domManip( args, table, callback );
                        });
                }
 
                if ( jQuery.isFunction(value) ) {
                        return this.each(function(i) {
                                var self = jQuery(this);
-                               args[0] = value.call(this, i, table ? self.html() : undefined);
+                               args[0] = value.call( this, i, table ? self.html() : undefined );
                                self.domManip( args, table, callback );
                        });
                }
 
                if ( this[0] ) {
-                       parent = value && value.parentNode;
-
-                       // If we're in a fragment, just use that instead of building a new one
-                       if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
-                               results = { fragment: parent };
-
-                       } else {
-                               results = jQuery.buildFragment( args, this, scripts );
-                       }
-
+                       results = jQuery.buildFragment( args, this, scripts );
                        fragment = results.fragment;
+                       first = fragment.firstChild;
 
                        if ( fragment.childNodes.length === 1 ) {
-                               first = fragment = fragment.firstChild;
-                       } else {
-                               first = fragment.firstChild;
+                               fragment = first;
                        }
 
                        if ( first ) {
                                table = table && jQuery.nodeName( first, "tr" );
 
-                               for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+                               // Use the original fragment for the last item instead of the first because it can end up
+                               // being emptied incorrectly in certain situations (#8070).
+                               // Fragments from the fragment cache must always be cloned and never used in place.
+                               for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
                                        callback.call(
-                                               table ?
-                                                       root(this[i], first) :
+                                               table && jQuery.nodeName( this[i], "table" ) ?
+                                                       findOrAppend( this[i], "tbody" ) :
                                                        this[i],
-                                               // Make sure that we do not leak memory by inadvertently discarding
-                                               // the original fragment (which might have attached data) instead of
-                                               // using it; in addition, use the original fragment object for the last
-                                               // item instead of first because it can end up being emptied incorrectly
-                                               // in certain situations (Bug #8070).
-                                               // Fragments from the fragment cache must always be cloned and never used
-                                               // in place.
-                                               results.cacheable || ( l > 1 && i < lastIndex ) ?
-                                                       jQuery.clone( fragment, true, true ) :
-                                                       fragment
+                                               i === iNoClone ?
+                                                       fragment :
+                                                       jQuery.clone( fragment, true, true )
                                        );
                                }
                        }
 
+                       // Fix #11809: Avoid leaking memory
+                       fragment = first = null;
+
                        if ( scripts.length ) {
                                jQuery.each( scripts, function( i, elem ) {
                                        if ( elem.src ) {
-                                               jQuery.ajax({
-                                                       type: "GET",
-                                                       global: false,
-                                                       url: elem.src,
-                                                       async: false,
-                                                       dataType: "script"
-                                               });
+                                               if ( jQuery.ajax ) {
+                                                       jQuery.ajax({
+                                                               url: elem.src,
+                                                               type: "GET",
+                                                               dataType: "script",
+                                                               async: false,
+                                                               global: false,
+                                                               "throws": true
+                                                       });
+                                               } else {
+                                                       jQuery.error("no ajax");
+                                               }
                                        } else {
-                                               jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+                                               jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
                                        }
 
                                        if ( elem.parentNode ) {
@@ -6089,11 +5816,8 @@ jQuery.fn.extend({
        }
 });
 
-function root( elem, cur ) {
-       return jQuery.nodeName(elem, "table") ?
-               (elem.getElementsByTagName("tbody")[0] ||
-               elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
-               elem;
+function findOrAppend( elem, tag ) {
+       return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
 }
 
 function cloneCopyEvent( src, dest ) {
@@ -6146,19 +5870,27 @@ function cloneFixAttributes( src, dest ) {
 
        nodeName = dest.nodeName.toLowerCase();
 
-       // IE6-8 fail to clone children inside object elements that use
-       // the proprietary classid attribute value (rather than the type
-       // attribute) to identify the type of content to display
        if ( nodeName === "object" ) {
-               dest.outerHTML = src.outerHTML;
+               // IE6-10 improperly clones children of object elements using classid.
+               // IE10 throws NoModificationAllowedError if parent is null, #12132.
+               if ( dest.parentNode ) {
+                       dest.outerHTML = src.outerHTML;
+               }
+
+               // This path appears unavoidable for IE9. When cloning an object
+               // element in IE9, the outerHTML strategy above is not sufficient.
+               // If the src has innerHTML and the destination does not,
+               // copy the src.innerHTML into the dest.innerHTML. #10324
+               if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
+                       dest.innerHTML = src.innerHTML;
+               }
 
-       } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+       } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
                // IE6-8 fails to persist the checked state of a cloned checkbox
                // or radio button. Worse, IE6-7 fail to give the cloned element
                // a checked appearance if the defaultChecked value isn't also set
-               if ( src.checked ) {
-                       dest.defaultChecked = dest.checked = src.checked;
-               }
+
+               dest.defaultChecked = dest.checked = src.checked;
 
                // IE6-7 get confused and end up setting the value of a cloned
                // checkbox/radio button to an empty string instead of "on"
@@ -6184,29 +5916,21 @@ function cloneFixAttributes( src, dest ) {
        // Event data gets referenced instead of copied if the expando
        // gets copied too
        dest.removeAttribute( jQuery.expando );
-
-       // Clear flags for bubbling special change/submit events, they must
-       // be reattached when the newly cloned events are first activated
-       dest.removeAttribute( "_submit_attached" );
-       dest.removeAttribute( "_change_attached" );
 }
 
-jQuery.buildFragment = function( args, nodes, scripts ) {
-       var fragment, cacheable, cacheresults, doc,
-       first = args[ 0 ];
+jQuery.buildFragment = function( args, context, scripts ) {
+       var fragment, cacheable, cachehit,
+               first = args[ 0 ];
 
-       // nodes may contain either an explicit document object,
-       // a jQuery collection or context object.
-       // If nodes[0] contains a valid object to assign to doc
-       if ( nodes && nodes[0] ) {
-               doc = nodes[0].ownerDocument || nodes[0];
-       }
+       // Set context from what may come in as undefined or a jQuery collection or a node
+       context = context || document;
+       context = (context[0] || context).ownerDocument || context[0] || context;
 
        // Ensure that an attr object doesn't incorrectly stand in as a document object
        // Chrome and Firefox seem to allow this to occur and will throw exception
        // Fixes #8950
-       if ( !doc.createDocumentFragment ) {
-               doc = document;
+       if ( typeof context.createDocumentFragment === "undefined" ) {
+               context = document;
        }
 
        // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
@@ -6214,26 +5938,26 @@ jQuery.buildFragment = function( args, nodes, scripts ) {
        // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
        // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
        // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
-       if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+       if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
                first.charAt(0) === "<" && !rnocache.test( first ) &&
                (jQuery.support.checkClone || !rchecked.test( first )) &&
                (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
 
+               // Mark cacheable and look for a hit
                cacheable = true;
-
-               cacheresults = jQuery.fragments[ first ];
-               if ( cacheresults && cacheresults !== 1 ) {
-                       fragment = cacheresults;
-               }
+               fragment = jQuery.fragments[ first ];
+               cachehit = fragment !== undefined;
        }
 
        if ( !fragment ) {
-               fragment = doc.createDocumentFragment();
-               jQuery.clean( args, doc, fragment, scripts );
-       }
+               fragment = context.createDocumentFragment();
+               jQuery.clean( args, context, fragment, scripts );
 
-       if ( cacheable ) {
-               jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+               // Update the cache, but only store false
+               // unless this is a second parsing of the same content
+               if ( cacheable ) {
+                       jQuery.fragments[ first ] = cachehit && fragment;
+               }
        }
 
        return { fragment: fragment, cacheable: cacheable };
@@ -6249,17 +5973,19 @@ jQuery.each({
        replaceAll: "replaceWith"
 }, function( name, original ) {
        jQuery.fn[ name ] = function( selector ) {
-               var ret = [],
+               var elems,
+                       i = 0,
+                       ret = [],
                        insert = jQuery( selector ),
+                       l = insert.length,
                        parent = this.length === 1 && this[0].parentNode;
 
-               if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+               if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
                        insert[ original ]( this[0] );
                        return this;
-
                } else {
-                       for ( var i = 0, l = insert.length; i < l; i++ ) {
-                               var elems = ( i > 0 ? this.clone(true) : this ).get();
+                       for ( ; i < l; i++ ) {
+                               elems = ( i > 0 ? this.clone(true) : this ).get();
                                jQuery( insert[i] )[ original ]( elems );
                                ret = ret.concat( elems );
                        }
@@ -6283,39 +6009,26 @@ function getAll( elem ) {
 
 // Used in clean, fixes the defaultChecked property
 function fixDefaultChecked( elem ) {
-       if ( elem.type === "checkbox" || elem.type === "radio" ) {
+       if ( rcheckableType.test( elem.type ) ) {
                elem.defaultChecked = elem.checked;
        }
 }
-// Finds all inputs and passes them to fixDefaultChecked
-function findInputs( elem ) {
-       var nodeName = ( elem.nodeName || "" ).toLowerCase();
-       if ( nodeName === "input" ) {
-               fixDefaultChecked( elem );
-       // Skip scripts, get other children
-       } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
-               jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
-       }
-}
-
-// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
-function shimCloneNode( elem ) {
-       var div = document.createElement( "div" );
-       safeFragment.appendChild( div );
-
-       div.innerHTML = elem.outerHTML;
-       return div.firstChild;
-}
 
 jQuery.extend({
        clone: function( elem, dataAndEvents, deepDataAndEvents ) {
                var srcElements,
                        destElements,
                        i,
-                       // IE<=8 does not properly clone detached, unknown element nodes
-                       clone = jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ?
-                               elem.cloneNode( true ) :
-                               shimCloneNode( elem );
+                       clone;
+
+               if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+                       clone = elem.cloneNode( true );
+
+               // IE<=8 does not properly clone detached, unknown element nodes
+               } else {
+                       fragmentDiv.innerHTML = elem.outerHTML;
+                       fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+               }
 
                if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
                                (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
@@ -6363,17 +6076,17 @@ jQuery.extend({
        },
 
        clean: function( elems, context, fragment, scripts ) {
-               var checkScriptType, script, j,
-                               ret = [];
-
-               context = context || document;
+               var j, safe, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
+                       i = 0,
+                       ret = [];
 
-               // !context.createElement fails in IE with an error but returns typeof 'object'
-               if ( typeof context.createElement === "undefined" ) {
-                       context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+               // Ensure that context is a document
+               if ( !context || typeof context.createDocumentFragment === "undefined" ) {
+                       context = document;
                }
 
-               for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+               // Use the already-created safe fragment if context permits
+               for ( safe = context === document && safeFragment; (elem = elems[i]) != null; i++ ) {
                        if ( typeof elem === "number" ) {
                                elem += "";
                        }
@@ -6387,27 +6100,17 @@ jQuery.extend({
                                if ( !rhtml.test( elem ) ) {
                                        elem = context.createTextNode( elem );
                                } else {
+                                       // Ensure a safe container in which to render the html
+                                       safe = safe || createSafeFragment( context );
+                                       div = div || safe.appendChild( context.createElement("div") );
+
                                        // Fix "XHTML"-style tags in all browsers
                                        elem = elem.replace(rxhtmlTag, "<$1></$2>");
 
-                                       // Trim whitespace, otherwise indexOf won't work as expected
-                                       var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
-                                               wrap = wrapMap[ tag ] || wrapMap._default,
-                                               depth = wrap[0],
-                                               div = context.createElement("div"),
-                                               safeChildNodes = safeFragment.childNodes,
-                                               remove;
-
-                                       // Append wrapper element to unknown element safe doc fragment
-                                       if ( context === document ) {
-                                               // Use the fragment we've already created for this document
-                                               safeFragment.appendChild( div );
-                                       } else {
-                                               // Use a fragment created with the owner document
-                                               createSafeFragment( context ).appendChild( div );
-                                       }
-
                                        // Go to html and back, then peel off extra wrappers
+                                       tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+                                       wrap = wrapMap[ tag ] || wrapMap._default;
+                                       depth = wrap[0];
                                        div.innerHTML = wrap[1] + elem + wrap[2];
 
                                        // Move to the right depth
@@ -6419,7 +6122,7 @@ jQuery.extend({
                                        if ( !jQuery.support.tbody ) {
 
                                                // String was a <table>, *may* have spurious <tbody>
-                                               var hasBody = rtbody.test(elem),
+                                               hasBody = rtbody.test(elem);
                                                        tbody = tag === "table" && !hasBody ?
                                                                div.firstChild && div.firstChild.childNodes :
 
@@ -6442,33 +6145,8 @@ jQuery.extend({
 
                                        elem = div.childNodes;
 
-                                       // Clear elements from DocumentFragment (safeFragment or otherwise)
-                                       // to avoid hoarding elements. Fixes #11356
-                                       if ( div ) {
-                                               div.parentNode.removeChild( div );
-
-                                               // Guard against -1 index exceptions in FF3.6
-                                               if ( safeChildNodes.length > 0 ) {
-                                                       remove = safeChildNodes[ safeChildNodes.length - 1 ];
-
-                                                       if ( remove && remove.parentNode ) {
-                                                               remove.parentNode.removeChild( remove );
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-
-                       // Resets defaultChecked for any radios and checkboxes
-                       // about to be appended to the DOM in IE 6/7 (#8060)
-                       var len;
-                       if ( !jQuery.support.appendChecked ) {
-                               if ( elem[0] && typeof (len = elem.length) === "number" ) {
-                                       for ( j = 0; j < len; j++ ) {
-                                               findInputs( elem[j] );
-                                       }
-                               } else {
-                                       findInputs( elem );
+                                       // Remember the top-level container for proper cleanup
+                                       div = safe.lastChild;
                                }
                        }
 
@@ -6479,22 +6157,51 @@ jQuery.extend({
                        }
                }
 
+               // Fix #11356: Clear elements from safeFragment
+               if ( div ) {
+                       safe.removeChild( div );
+                       elem = div = safe = null;
+               }
+
+               // Reset defaultChecked for any radios and checkboxes
+               // about to be appended to the DOM in IE 6/7 (#8060)
+               if ( !jQuery.support.appendChecked ) {
+                       for ( i = 0; (elem = ret[i]) != null; i++ ) {
+                               if ( jQuery.nodeName( elem, "input" ) ) {
+                                       fixDefaultChecked( elem );
+                               } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
+                                       jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+                               }
+                       }
+               }
+
+               // Append elements to a provided document fragment
                if ( fragment ) {
-                       checkScriptType = function( elem ) {
-                               return !elem.type || rscriptType.test( elem.type );
+                       // Special handling of each script element
+                       handleScript = function( elem ) {
+                               // Check if we consider it executable
+                               if ( !elem.type || rscriptType.test( elem.type ) ) {
+                                       // Detach the script and store it in the scripts array (if provided) or the fragment
+                                       // Return truthy to indicate that it has been handled
+                                       return scripts ?
+                                               scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+                                               fragment.appendChild( elem );
+                               }
                        };
-                       for ( i = 0; ret[i]; i++ ) {
-                               script = ret[i];
-                               if ( scripts && jQuery.nodeName( script, "script" ) && (!script.type || rscriptType.test( script.type )) ) {
-                                       scripts.push( script.parentNode ? script.parentNode.removeChild( script ) : script );
 
-                               } else {
-                                       if ( script.nodeType === 1 ) {
-                                               var jsTags = jQuery.grep( script.getElementsByTagName( "script" ), checkScriptType );
+                       for ( i = 0; (elem = ret[i]) != null; i++ ) {
+                               // Check if we're done after handling an executable script
+                               if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+                                       // Append to fragment and handle embedded scripts
+                                       fragment.appendChild( elem );
+                                       if ( typeof elem.getElementsByTagName !== "undefined" ) {
+                                               // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+                                               jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
 
+                                               // Splice the scripts into ret after their former ancestor and advance our index beyond them
                                                ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+                                               i += jsTags.length;
                                        }
-                                       fragment.appendChild( script );
                                }
                        }
                }
@@ -6502,81 +6209,250 @@ jQuery.extend({
                return ret;
        },
 
-       cleanData: function( elems ) {
-               var data, id,
+       cleanData: function( elems, /* internal */ acceptData ) {
+               var data, id, elem, type,
+                       i = 0,
+                       internalKey = jQuery.expando,
                        cache = jQuery.cache,
-                       special = jQuery.event.special,
-                       deleteExpando = jQuery.support.deleteExpando;
+                       deleteExpando = jQuery.support.deleteExpando,
+                       special = jQuery.event.special;
 
-               for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
-                       if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
-                               continue;
-                       }
+               for ( ; (elem = elems[i]) != null; i++ ) {
 
-                       id = elem[ jQuery.expando ];
+                       if ( acceptData || jQuery.acceptData( elem ) ) {
 
-                       if ( id ) {
-                               data = cache[ id ];
+                               id = elem[ internalKey ];
+                               data = id && cache[ id ];
 
-                               if ( data && data.events ) {
-                                       for ( var type in data.events ) {
-                                               if ( special[ type ] ) {
-                                                       jQuery.event.remove( elem, type );
+                               if ( data ) {
+                                       if ( data.events ) {
+                                               for ( type in data.events ) {
+                                                       if ( special[ type ] ) {
+                                                               jQuery.event.remove( elem, type );
 
-                                               // This is a shortcut to avoid jQuery.event.remove's overhead
-                                               } else {
-                                                       jQuery.removeEvent( elem, type, data.handle );
+                                                       // This is a shortcut to avoid jQuery.event.remove's overhead
+                                                       } else {
+                                                               jQuery.removeEvent( elem, type, data.handle );
+                                                       }
                                                }
                                        }
 
-                                       // Null the DOM reference to avoid IE6/7/8 leak (#7054)
-                                       if ( data.handle ) {
-                                               data.handle.elem = null;
-                                       }
-                               }
+                                       // Remove cache only if it was not already removed by jQuery.event.remove
+                                       if ( cache[ id ] ) {
 
-                               if ( deleteExpando ) {
-                                       delete elem[ jQuery.expando ];
+                                               delete cache[ id ];
 
-                               } else if ( elem.removeAttribute ) {
-                                       elem.removeAttribute( jQuery.expando );
-                               }
+                                               // IE does not allow us to delete expando properties from nodes,
+                                               // nor does it have a removeAttribute function on Document nodes;
+                                               // we must handle all of these cases
+                                               if ( deleteExpando ) {
+                                                       delete elem[ internalKey ];
+
+                                               } else if ( elem.removeAttribute ) {
+                                                       elem.removeAttribute( internalKey );
+
+                                               } else {
+                                                       elem[ internalKey ] = null;
+                                               }
 
-                               delete cache[ id ];
+                                               jQuery.deletedIds.push( id );
+                                       }
+                               }
                        }
                }
        }
 });
+// Limit scope pollution from any deprecated API
+(function() {
+
+var matched, browser;
+
+// Use of jQuery.browser is frowned upon.
+// More details: http://api.jquery.com/jQuery.browser
+// jQuery.uaMatch maintained for back-compat
+jQuery.uaMatch = function( ua ) {
+       ua = ua.toLowerCase();
+
+       var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+               /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+               /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+               /(msie) ([\w.]+)/.exec( ua ) ||
+               ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+               [];
+
+       return {
+               browser: match[ 1 ] || "",
+               version: match[ 2 ] || "0"
+       };
+};
 
+matched = jQuery.uaMatch( navigator.userAgent );
+browser = {};
+
+if ( matched.browser ) {
+       browser[ matched.browser ] = true;
+       browser.version = matched.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+// Maintained for back-compat only
+if ( browser.webkit ) {
+       browser.safari = true;
+}
 
+jQuery.browser = browser;
 
+jQuery.sub = function() {
+       function jQuerySub( selector, context ) {
+               return new jQuerySub.fn.init( selector, context );
+       }
+       jQuery.extend( true, jQuerySub, this );
+       jQuerySub.superclass = this;
+       jQuerySub.fn = jQuerySub.prototype = this();
+       jQuerySub.fn.constructor = jQuerySub;
+       jQuerySub.sub = this.sub;
+       jQuerySub.fn.init = function init( selector, context ) {
+               if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+                       context = jQuerySub( context );
+               }
 
-var ralpha = /alpha\([^)]*\)/i,
+               return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+       };
+       jQuerySub.fn.init.prototype = jQuerySub.fn;
+       var rootjQuerySub = jQuerySub(document);
+       return jQuerySub;
+};
+       
+})();
+var curCSS, iframe, iframeDoc,
+       ralpha = /alpha\([^)]*\)/i,
        ropacity = /opacity=([^)]*)/,
-       // fixed for IE9, see #8346
-       rupper = /([A-Z]|^ms)/g,
-       rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
-       rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
-       rrelNum = /^([\-+])=([\-+.\de]+)/,
+       rposition = /^(top|right|bottom|left)$/,
        rmargin = /^margin/,
+       rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+       rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+       rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
+       elemdisplay = {},
 
        cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+       cssNormalTransform = {
+               letterSpacing: 0,
+               fontWeight: 400,
+               lineHeight: 1
+       },
 
-       // order is important!
        cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+       cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
 
-       curCSS,
+       eventsToggle = jQuery.fn.toggle;
 
-       getComputedStyle,
-       currentStyle;
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
 
-jQuery.fn.css = function( name, value ) {
-       return jQuery.access( this, function( elem, name, value ) {
-               return value !== undefined ?
-                       jQuery.style( elem, name, value ) :
-                       jQuery.css( elem, name );
-       }, name, value, arguments.length > 1 );
-};
+       // shortcut for names that are not vendor prefixed
+       if ( name in style ) {
+               return name;
+       }
+
+       // check for vendor prefixed names
+       var capName = name.charAt(0).toUpperCase() + name.slice(1),
+               origName = name,
+               i = cssPrefixes.length;
+
+       while ( i-- ) {
+               name = cssPrefixes[ i ] + capName;
+               if ( name in style ) {
+                       return name;
+               }
+       }
+
+       return origName;
+}
+
+function isHidden( elem, el ) {
+       elem = el || elem;
+       return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+function showHide( elements, show ) {
+       var elem, display,
+               values = [],
+               index = 0,
+               length = elements.length;
+
+       for ( ; index < length; index++ ) {
+               elem = elements[ index ];
+               if ( !elem.style ) {
+                       continue;
+               }
+               values[ index ] = jQuery._data( elem, "olddisplay" );
+               if ( show ) {
+                       // Reset the inline display of this element to learn if it is
+                       // being hidden by cascaded rules or not
+                       if ( !values[ index ] && elem.style.display === "none" ) {
+                               elem.style.display = "";
+                       }
+
+                       // Set elements which have been overridden with display: none
+                       // in a stylesheet to whatever the default browser style is
+                       // for such an element
+                       if ( elem.style.display === "" && isHidden( elem ) ) {
+                               values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+                       }
+               } else {
+                       display = curCSS( elem, "display" );
+
+                       if ( !values[ index ] && display !== "none" ) {
+                               jQuery._data( elem, "olddisplay", display );
+                       }
+               }
+       }
+
+       // Set the display of most of the elements in a second loop
+       // to avoid the constant reflow
+       for ( index = 0; index < length; index++ ) {
+               elem = elements[ index ];
+               if ( !elem.style ) {
+                       continue;
+               }
+               if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+                       elem.style.display = show ? values[ index ] || "" : "none";
+               }
+       }
+
+       return elements;
+}
+
+jQuery.fn.extend({
+       css: function( name, value ) {
+               return jQuery.access( this, function( elem, name, value ) {
+                       return value !== undefined ?
+                               jQuery.style( elem, name, value ) :
+                               jQuery.css( elem, name );
+               }, name, value, arguments.length > 1 );
+       },
+       show: function() {
+               return showHide( this, true );
+       },
+       hide: function() {
+               return showHide( this );
+       },
+       toggle: function( state, fn2 ) {
+               var bool = typeof state === "boolean";
+
+               if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
+                       return eventsToggle.apply( this, arguments );
+               }
+
+               return this.each(function() {
+                       if ( bool ? state : isHidden( this ) ) {
+                               jQuery( this ).show();
+                       } else {
+                               jQuery( this ).hide();
+                       }
+               });
+       }
+});
 
 jQuery.extend({
        // Add in style property hooks for overriding the default
@@ -6589,8 +6465,6 @@ jQuery.extend({
                                        var ret = curCSS( elem, "opacity" );
                                        return ret === "" ? "1" : ret;
 
-                               } else {
-                                       return elem.style.opacity;
                                }
                        }
                }
@@ -6623,10 +6497,15 @@ jQuery.extend({
                }
 
                // Make sure that we're working with the right name
-               var ret, type, origName = jQuery.camelCase( name ),
-                       style = elem.style, hooks = jQuery.cssHooks[ origName ];
+               var ret, type, hooks,
+                       origName = jQuery.camelCase( name ),
+                       style = elem.style;
+
+               name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
 
-               name = jQuery.cssProps[ origName ] || origName;
+               // gets hook for the prefixed version
+               // followed by the unprefixed version
+               hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
 
                // Check if we're setting a value
                if ( value !== undefined ) {
@@ -6634,7 +6513,7 @@ jQuery.extend({
 
                        // convert relative number strings (+= or -=) to relative numbers. #7345
                        if ( type === "string" && (ret = rrelNum.exec( value )) ) {
-                               value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+                               value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
                                // Fixes bug #9237
                                type = "number";
                        }
@@ -6650,7 +6529,7 @@ jQuery.extend({
                        }
 
                        // If a hook was provided, use that value, otherwise just set the specified value
-                       if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+                       if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
                                // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
                                // Fixes bug #5509
                                try {
@@ -6669,33 +6548,44 @@ jQuery.extend({
                }
        },
 
-       css: function( elem, name, extra ) {
-               var ret, hooks;
+       css: function( elem, name, numeric, extra ) {
+               var val, num, hooks,
+                       origName = jQuery.camelCase( name );
 
                // Make sure that we're working with the right name
-               name = jQuery.camelCase( name );
-               hooks = jQuery.cssHooks[ name ];
-               name = jQuery.cssProps[ name ] || name;
+               name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
 
-               // cssFloat needs a special treatment
-               if ( name === "cssFloat" ) {
-                       name = "float";
-               }
+               // gets hook for the prefixed version
+               // followed by the unprefixed version
+               hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
 
                // If a hook was provided get the computed value from there
-               if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
-                       return ret;
+               if ( hooks && "get" in hooks ) {
+                       val = hooks.get( elem, true, extra );
+               }
 
                // Otherwise, if a way to get the computed value exists, use that
-               } else if ( curCSS ) {
-                       return curCSS( elem, name );
+               if ( val === undefined ) {
+                       val = curCSS( elem, name );
+               }
+
+               //convert "normal" to computed value
+               if ( val === "normal" && name in cssNormalTransform ) {
+                       val = cssNormalTransform[ name ];
+               }
+
+               // Return, converting to number if forced or a qualifier was provided and val looks numeric
+               if ( numeric || extra !== undefined ) {
+                       num = parseFloat( val );
+                       return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
                }
+               return val;
        },
 
        // A method for quickly swapping in/out CSS properties to get correct calculations
        swap: function( elem, options, callback ) {
-               var old = {},
-                       ret, name;
+               var ret, name,
+                       old = {};
 
                // Remember the old values, and insert the new ones
                for ( name in options ) {
@@ -6714,49 +6604,51 @@ jQuery.extend({
        }
 });
 
-// DEPRECATED in 1.3, Use jQuery.css() instead
-jQuery.curCSS = jQuery.css;
-
-if ( document.defaultView && document.defaultView.getComputedStyle ) {
-       getComputedStyle = function( elem, name ) {
-               var ret, defaultView, computedStyle, width,
+// NOTE: To any future maintainer, we've used both window.getComputedStyle
+// and getComputedStyle here to produce a better gzip size
+if ( window.getComputedStyle ) {
+       curCSS = function( elem, name ) {
+               var ret, width, minWidth, maxWidth,
+                       computed = getComputedStyle( elem, null ),
                        style = elem.style;
 
-               name = name.replace( rupper, "-$1" ).toLowerCase();
+               if ( computed ) {
 
-               if ( (defaultView = elem.ownerDocument.defaultView) &&
-                               (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
-
-                       ret = computedStyle.getPropertyValue( name );
+                       ret = computed[ name ];
                        if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
                                ret = jQuery.style( elem, name );
                        }
-               }
 
-               // A tribute to the "awesome hack by Dean Edwards"
-               // WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
-               // which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
-               if ( !jQuery.support.pixelMargin && computedStyle && rmargin.test( name ) && rnumnonpx.test( ret ) ) {
-                       width = style.width;
-                       style.width = ret;
-                       ret = computedStyle.width;
-                       style.width = width;
+                       // A tribute to the "awesome hack by Dean Edwards"
+                       // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+                       // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+                       // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+                       if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+                               width = style.width;
+                               minWidth = style.minWidth;
+                               maxWidth = style.maxWidth;
+
+                               style.minWidth = style.maxWidth = style.width = ret;
+                               ret = computed.width;
+
+                               style.width = width;
+                               style.minWidth = minWidth;
+                               style.maxWidth = maxWidth;
+                       }
                }
 
                return ret;
        };
-}
-
-if ( document.documentElement.currentStyle ) {
-       currentStyle = function( elem, name ) {
-               var left, rsLeft, uncomputed,
+} else if ( document.documentElement.currentStyle ) {
+       curCSS = function( elem, name ) {
+               var left, rsLeft,
                        ret = elem.currentStyle && elem.currentStyle[ name ],
                        style = elem.style;
 
                // Avoid setting ret to empty string here
                // so we don't default to auto
-               if ( ret == null && style && (uncomputed = style[ name ]) ) {
-                       ret = uncomputed;
+               if ( ret == null && style && style[ name ] ) {
+                       ret = style[ name ];
                }
 
                // From the awesome hack by Dean Edwards
@@ -6764,7 +6656,9 @@ if ( document.documentElement.currentStyle ) {
 
                // If we're not dealing with a regular pixel number
                // but a number that has a weird ending, we need to convert it to pixels
-               if ( rnumnonpx.test( ret ) ) {
+               // but not position css attributes, as those are proportional to the parent element instead
+               // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+               if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
 
                        // Remember the original values
                        left = style.left;
@@ -6774,81 +6668,156 @@ if ( document.documentElement.currentStyle ) {
                        if ( rsLeft ) {
                                elem.runtimeStyle.left = elem.currentStyle.left;
                        }
-                       style.left = name === "fontSize" ? "1em" : ret;
-                       ret = style.pixelLeft + "px";
+                       style.left = name === "fontSize" ? "1em" : ret;
+                       ret = style.pixelLeft + "px";
+
+                       // Revert the changed values
+                       style.left = left;
+                       if ( rsLeft ) {
+                               elem.runtimeStyle.left = rsLeft;
+                       }
+               }
+
+               return ret === "" ? "auto" : ret;
+       };
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+       var matches = rnumsplit.exec( value );
+       return matches ?
+                       Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+                       value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
+       var i = extra === ( isBorderBox ? "border" : "content" ) ?
+               // If we already have the right measurement, avoid augmentation
+               4 :
+               // Otherwise initialize for horizontal or vertical properties
+               name === "width" ? 1 : 0,
+
+               val = 0;
+
+       for ( ; i < 4; i += 2 ) {
+               // both box models exclude margin, so add it if we want it
+               if ( extra === "margin" ) {
+                       // we use jQuery.css instead of curCSS here
+                       // because of the reliableMarginRight CSS hook!
+                       val += jQuery.css( elem, extra + cssExpand[ i ], true );
+               }
+
+               // From this point on we use curCSS for maximum performance (relevant in animations)
+               if ( isBorderBox ) {
+                       // border-box includes padding, so remove it if we want content
+                       if ( extra === "content" ) {
+                               val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+                       }
+
+                       // at this point, extra isn't border nor margin, so remove border
+                       if ( extra !== "margin" ) {
+                               val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+                       }
+               } else {
+                       // at this point, extra isn't content, so add padding
+                       val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
 
-                       // Revert the changed values
-                       style.left = left;
-                       if ( rsLeft ) {
-                               elem.runtimeStyle.left = rsLeft;
+                       // at this point, extra isn't content nor padding, so add border
+                       if ( extra !== "padding" ) {
+                               val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
                        }
                }
+       }
 
-               return ret === "" ? "auto" : ret;
-       };
+       return val;
 }
 
-curCSS = getComputedStyle || currentStyle;
-
 function getWidthOrHeight( elem, name, extra ) {
 
-       // Start with offset property
+       // Start with offset property, which is equivalent to the border-box value
        var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
-               i = name === "width" ? 1 : 0,
-               len = 4;
-
-       if ( val > 0 ) {
-               if ( extra !== "border" ) {
-                       for ( ; i < len; i += 2 ) {
-                               if ( !extra ) {
-                                       val -= parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
-                               }
-                               if ( extra === "margin" ) {
-                                       val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
-                               } else {
-                                       val -= parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
-                               }
-                       }
+               valueIsBorderBox = true,
+               isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
+
+       if ( val <= 0 ) {
+               // Fall back to computed then uncomputed css if necessary
+               val = curCSS( elem, name );
+               if ( val < 0 || val == null ) {
+                       val = elem.style[ name ];
                }
 
-               return val + "px";
-       }
+               // Computed unit is not pixels. Stop here and return.
+               if ( rnumnonpx.test(val) ) {
+                       return val;
+               }
+
+               // we need the check for style in case a browser which returns unreliable values
+               // for getComputedStyle silently falls back to the reliable elem.style
+               valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
 
-       // Fall back to computed then uncomputed css if necessary
-       val = curCSS( elem, name );
-       if ( val < 0 || val == null ) {
-               val = elem.style[ name ];
+               // Normalize "", auto, and prepare for extra
+               val = parseFloat( val ) || 0;
        }
 
-       // Computed unit is not pixels. Stop here and return.
-       if ( rnumnonpx.test(val) ) {
-               return val;
+       // use the active box-sizing model to add/subtract irrelevant styles
+       return ( val +
+               augmentWidthOrHeight(
+                       elem,
+                       name,
+                       extra || ( isBorderBox ? "border" : "content" ),
+                       valueIsBorderBox
+               )
+       ) + "px";
+}
+
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+       if ( elemdisplay[ nodeName ] ) {
+               return elemdisplay[ nodeName ];
        }
 
-       // Normalize "", auto, and prepare for extra
-       val = parseFloat( val ) || 0;
+       var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+               display = elem.css("display");
+       elem.remove();
+
+       // If the simple way fails,
+       // get element's real default display by attaching it to a temp iframe
+       if ( display === "none" || display === "" ) {
+               // Use the already-created iframe if possible
+               iframe = document.body.appendChild(
+                       iframe || jQuery.extend( document.createElement("iframe"), {
+                               frameBorder: 0,
+                               width: 0,
+                               height: 0
+                       })
+               );
 
-       // Add padding, border, margin
-       if ( extra ) {
-               for ( ; i < len; i += 2 ) {
-                       val += parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
-                       if ( extra !== "padding" ) {
-                               val += parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
-                       }
-                       if ( extra === "margin" ) {
-                               val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ]) ) || 0;
-                       }
+               // Create a cacheable copy of the iframe document on first call.
+               // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+               // document to it; WebKit & Firefox won't allow reusing the iframe document.
+               if ( !iframeDoc || !iframe.createElement ) {
+                       iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+                       iframeDoc.write("<!doctype html><html><body>");
+                       iframeDoc.close();
                }
+
+               elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+               display = curCSS( elem, "display" );
+               document.body.removeChild( iframe );
        }
 
-       return val + "px";
+       // Store the correct default display
+       elemdisplay[ nodeName ] = display;
+
+       return display;
 }
 
 jQuery.each([ "height", "width" ], function( i, name ) {
        jQuery.cssHooks[ name ] = {
                get: function( elem, computed, extra ) {
                        if ( computed ) {
-                               if ( elem.offsetWidth !== 0 ) {
+                               if ( elem.offsetWidth !== 0 || curCSS( elem, "display" ) !== "none" ) {
                                        return getWidthOrHeight( elem, name, extra );
                                } else {
                                        return jQuery.swap( elem, cssShow, function() {
@@ -6858,10 +6827,15 @@ jQuery.each([ "height", "width" ], function( i, name ) {
                        }
                },
 
-               set: function( elem, value ) {
-                       return rnum.test( value ) ?
-                               value + "px" :
-                               value;
+               set: function( elem, value, extra ) {
+                       return setPositiveNumber( elem, value, extra ?
+                               augmentWidthOrHeight(
+                                       elem,
+                                       name,
+                                       extra,
+                                       jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
+                               ) : 0
+                       );
                }
        };
 });
@@ -6871,7 +6845,7 @@ if ( !jQuery.support.opacity ) {
                get: function( elem, computed ) {
                        // IE uses filters for opacity
                        return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
-                               ( parseFloat( RegExp.$1 ) / 100 ) + "" :
+                               ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
                                computed ? "1" : "";
                },
 
@@ -6886,7 +6860,8 @@ if ( !jQuery.support.opacity ) {
                        style.zoom = 1;
 
                        // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
-                       if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+                       if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+                               style.removeAttribute ) {
 
                                // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
                                // if "filter:" is present at all, clearType is disabled, we want to avoid this
@@ -6907,9 +6882,9 @@ if ( !jQuery.support.opacity ) {
        };
 }
 
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
 jQuery(function() {
-       // This hook cannot be added until DOM ready because the support test
-       // for it is not run until after DOM ready
        if ( !jQuery.support.reliableMarginRight ) {
                jQuery.cssHooks.marginRight = {
                        get: function( elem, computed ) {
@@ -6917,22 +6892,35 @@ jQuery(function() {
                                // Work around by temporarily setting element display to inline-block
                                return jQuery.swap( elem, { "display": "inline-block" }, function() {
                                        if ( computed ) {
-                                               return curCSS( elem, "margin-right" );
-                                       } else {
-                                               return elem.style.marginRight;
+                                               return curCSS( elem, "marginRight" );
                                        }
                                });
                        }
                };
        }
+
+       // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+       // getComputedStyle returns percent when specified for top/left/bottom/right
+       // rather than make the css module depend on the offset module, we just check for it here
+       if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+               jQuery.each( [ "top", "left" ], function( i, prop ) {
+                       jQuery.cssHooks[ prop ] = {
+                               get: function( elem, computed ) {
+                                       if ( computed ) {
+                                               var ret = curCSS( elem, prop );
+                                               // if curCSS returns percentage, fallback to offset
+                                               return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
+                                       }
+                               }
+                       };
+               });
+       }
+
 });
 
 if ( jQuery.expr && jQuery.expr.filters ) {
        jQuery.expr.filters.hidden = function( elem ) {
-               var width = elem.offsetWidth,
-                       height = elem.offsetHeight;
-
-               return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+               return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
        };
 
        jQuery.expr.filters.visible = function( elem ) {
@@ -6946,7 +6934,6 @@ jQuery.each({
        padding: "",
        border: "Width"
 }, function( prefix, suffix ) {
-
        jQuery.cssHooks[ prefix + suffix ] = {
                expand: function( value ) {
                        var i,
@@ -6963,27 +6950,127 @@ jQuery.each({
                        return expanded;
                }
        };
+
+       if ( !rmargin.test( prefix ) ) {
+               jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+       }
+});
+var r20 = /%20/g,
+       rbracket = /\[\]$/,
+       rCRLF = /\r?\n/g,
+       rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+       rselectTextarea = /^(?:select|textarea)/i;
+
+jQuery.fn.extend({
+       serialize: function() {
+               return jQuery.param( this.serializeArray() );
+       },
+       serializeArray: function() {
+               return this.map(function(){
+                       return this.elements ? jQuery.makeArray( this.elements ) : this;
+               })
+               .filter(function(){
+                       return this.name && !this.disabled &&
+                               ( this.checked || rselectTextarea.test( this.nodeName ) ||
+                                       rinput.test( this.type ) );
+               })
+               .map(function( i, elem ){
+                       var val = jQuery( this ).val();
+
+                       return val == null ?
+                               null :
+                               jQuery.isArray( val ) ?
+                                       jQuery.map( val, function( val, i ){
+                                               return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+                                       }) :
+                                       { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+               }).get();
+       }
 });
 
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+       var prefix,
+               s = [],
+               add = function( key, value ) {
+                       // If value is a function, invoke it and return its value
+                       value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+                       s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+               };
+
+       // Set traditional to true for jQuery <= 1.3.2 behavior.
+       if ( traditional === undefined ) {
+               traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+       }
+
+       // If an array was passed in, assume that it is an array of form elements.
+       if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+               // Serialize the form elements
+               jQuery.each( a, function() {
+                       add( this.name, this.value );
+               });
 
+       } else {
+               // If traditional, encode the "old" way (the way 1.3.2 or older
+               // did it), otherwise encode params recursively.
+               for ( prefix in a ) {
+                       buildParams( prefix, a[ prefix ], traditional, add );
+               }
+       }
 
+       // Return the resulting serialization
+       return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+       var name;
+
+       if ( jQuery.isArray( obj ) ) {
+               // Serialize array item.
+               jQuery.each( obj, function( i, v ) {
+                       if ( traditional || rbracket.test( prefix ) ) {
+                               // Treat each array item as a scalar.
+                               add( prefix, v );
+
+                       } else {
+                               // If array item is non-scalar (array or object), encode its
+                               // numeric index to resolve deserialization ambiguity issues.
+                               // Note that rack (as of 1.0.0) can't currently deserialize
+                               // nested arrays properly, and attempting to do so may cause
+                               // a server error. Possible fixes are to modify rack's
+                               // deserialization algorithm or to provide an option or flag
+                               // to force array serialization to be shallow.
+                               buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+                       }
+               });
+
+       } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+               // Serialize object item.
+               for ( name in obj ) {
+                       buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+               }
+
+       } else {
+               // Serialize scalar item.
+               add( prefix, obj );
+       }
+}
+var // Document location
+       ajaxLocation,
+       // Document location segments
+       ajaxLocParts,
 
-var r20 = /%20/g,
-       rbracket = /\[\]$/,
-       rCRLF = /\r?\n/g,
        rhash = /#.*$/,
        rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
-       rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
        // #7653, #8125, #8152: local protocol detection
        rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
        rnoContent = /^(?:GET|HEAD)$/,
        rprotocol = /^\/\//,
        rquery = /\?/,
        rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
-       rselectTextarea = /^(?:select|textarea)/i,
-       rspacesAjax = /\s+/,
        rts = /([?&])_=[^&]*/,
-       rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+       rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
 
        // Keep a copy of the old load method
        _load = jQuery.fn.load,
@@ -7006,12 +7093,6 @@ var r20 = /%20/g,
         */
        transports = {},
 
-       // Document location
-       ajaxLocation,
-
-       // Document location segments
-       ajaxLocParts,
-
        // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
        allTypes = ["*/"] + ["*"];
 
@@ -7041,14 +7122,12 @@ function addToPrefiltersOrTransports( structure ) {
                        dataTypeExpression = "*";
                }
 
-               if ( jQuery.isFunction( func ) ) {
-                       var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
-                               i = 0,
-                               length = dataTypes.length,
-                               dataType,
-                               list,
-                               placeBefore;
+               var dataType, list, placeBefore,
+                       dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),
+                       i = 0,
+                       length = dataTypes.length;
 
+               if ( jQuery.isFunction( func ) ) {
                        // For each dataType in the dataTypeExpression
                        for ( ; i < length; i++ ) {
                                dataType = dataTypes[ i ];
@@ -7075,11 +7154,11 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX
 
        inspected[ dataType ] = true;
 
-       var list = structure[ dataType ],
+       var selection,
+               list = structure[ dataType ],
                i = 0,
                length = list ? list.length : 0,
-               executeOnly = ( structure === prefilters ),
-               selection;
+               executeOnly = ( structure === prefilters );
 
        for ( ; i < length && ( executeOnly || !selection ); i++ ) {
                selection = list[ i ]( options, originalOptions, jqXHR );
@@ -7122,109 +7201,75 @@ function ajaxExtend( target, src ) {
        }
 }
 
-jQuery.fn.extend({
-       load: function( url, params, callback ) {
-               if ( typeof url !== "string" && _load ) {
-                       return _load.apply( this, arguments );
+jQuery.fn.load = function( url, params, callback ) {
+       if ( typeof url !== "string" && _load ) {
+               return _load.apply( this, arguments );
+       }
 
-               // Don't do a request if no elements are being requested
-               } else if ( !this.length ) {
-                       return this;
-               }
+       // Don't do a request if no elements are being requested
+       if ( !this.length ) {
+               return this;
+       }
 
-               var off = url.indexOf( " " );
-               if ( off >= 0 ) {
-                       var selector = url.slice( off, url.length );
-                       url = url.slice( 0, off );
-               }
+       var selector, type, response,
+               self = this,
+               off = url.indexOf(" ");
+
+       if ( off >= 0 ) {
+               selector = url.slice( off, url.length );
+               url = url.slice( 0, off );
+       }
+
+       // If it's a function
+       if ( jQuery.isFunction( params ) ) {
 
-               // Default to a GET request
-               var type = "GET";
+               // We assume that it's the callback
+               callback = params;
+               params = undefined;
 
-               // If the second parameter was provided
-               if ( params ) {
-                       // If it's a function
-                       if ( jQuery.isFunction( params ) ) {
-                               // We assume that it's the callback
-                               callback = params;
-                               params = undefined;
+       // Otherwise, build a param string
+       } else if ( typeof params === "object" ) {
+               type = "POST";
+       }
+
+       // Request the remote document
+       jQuery.ajax({
+               url: url,
 
-                       // Otherwise, build a param string
-                       } else if ( typeof params === "object" ) {
-                               params = jQuery.param( params, jQuery.ajaxSettings.traditional );
-                               type = "POST";
+               // if "type" variable is undefined, then "GET" method will be used
+               type: type,
+               dataType: "html",
+               data: params,
+               complete: function( jqXHR, status ) {
+                       if ( callback ) {
+                               self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
                        }
                }
+       }).done(function( responseText ) {
 
-               var self = this;
+               // Save response for use in complete callback
+               response = arguments;
 
-               // Request the remote document
-               jQuery.ajax({
-                       url: url,
-                       type: type,
-                       dataType: "html",
-                       data: params,
-                       // Complete callback (responseText is used internally)
-                       complete: function( jqXHR, status, responseText ) {
-                               // Store the response as specified by the jqXHR object
-                               responseText = jqXHR.responseText;
-                               // If successful, inject the HTML into all the matched elements
-                               if ( jqXHR.isResolved() ) {
-                                       // #4825: Get the actual response in case
-                                       // a dataFilter is present in ajaxSettings
-                                       jqXHR.done(function( r ) {
-                                               responseText = r;
-                                       });
-                                       // See if a selector was specified
-                                       self.html( selector ?
-                                               // Create a dummy div to hold the results
-                                               jQuery("<div>")
-                                                       // inject the contents of the document in, removing the scripts
-                                                       // to avoid any 'Permission Denied' errors in IE
-                                                       .append(responseText.replace(rscript, ""))
-
-                                                       // Locate the specified elements
-                                                       .find(selector) :
-
-                                               // If not, just inject the full result
-                                               responseText );
-                               }
+               // See if a selector was specified
+               self.html( selector ?
 
-                               if ( callback ) {
-                                       self.each( callback, [ responseText, status, jqXHR ] );
-                               }
-                       }
-               });
+                       // Create a dummy div to hold the results
+                       jQuery("<div>")
 
-               return this;
-       },
+                               // inject the contents of the document in, removing the scripts
+                               // to avoid any 'Permission Denied' errors in IE
+                               .append( responseText.replace( rscript, "" ) )
 
-       serialize: function() {
-               return jQuery.param( this.serializeArray() );
-       },
+                               // Locate the specified elements
+                               .find( selector ) :
 
-       serializeArray: function() {
-               return this.map(function(){
-                       return this.elements ? jQuery.makeArray( this.elements ) : this;
-               })
-               .filter(function(){
-                       return this.name && !this.disabled &&
-                               ( this.checked || rselectTextarea.test( this.nodeName ) ||
-                                       rinput.test( this.type ) );
-               })
-               .map(function( i, elem ){
-                       var val = jQuery( this ).val();
+                       // If not, just inject the full result
+                       responseText );
 
-                       return val == null ?
-                               null :
-                               jQuery.isArray( val ) ?
-                                       jQuery.map( val, function( val, i ){
-                                               return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
-                                       }) :
-                                       { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
-               }).get();
-       }
-});
+       });
+
+       return this;
+};
 
 // Attach a bunch of functions for handling common AJAX events
 jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
@@ -7293,6 +7338,7 @@ jQuery.extend({
                username: null,
                password: null,
                cache: null,
+               throws: false,
                traditional: false,
                headers: {},
                */
@@ -7359,7 +7405,22 @@ jQuery.extend({
                // Force options to be an object
                options = options || {};
 
-               var // Create the final options object
+               var // ifModified key
+                       ifModifiedKey,
+                       // Response headers
+                       responseHeadersString,
+                       responseHeaders,
+                       // transport
+                       transport,
+                       // timeout handle
+                       timeoutTimer,
+                       // Cross-domain detection vars
+                       parts,
+                       // To know if global events are to be dispatched
+                       fireGlobals,
+                       // Loop variable
+                       i,
+                       // Create the final options object
                        s = jQuery.ajaxSetup( {}, options ),
                        // Callbacks context
                        callbackContext = s.context || s,
@@ -7374,26 +7435,13 @@ jQuery.extend({
                        completeDeferred = jQuery.Callbacks( "once memory" ),
                        // Status-dependent callbacks
                        statusCode = s.statusCode || {},
-                       // ifModified key
-                       ifModifiedKey,
                        // Headers (they are sent all at once)
                        requestHeaders = {},
                        requestHeadersNames = {},
-                       // Response headers
-                       responseHeadersString,
-                       responseHeaders,
-                       // transport
-                       transport,
-                       // timeout handle
-                       timeoutTimer,
-                       // Cross-domain detection vars
-                       parts,
                        // The jqXHR state
                        state = 0,
-                       // To know if global events are to be dispatched
-                       fireGlobals,
-                       // Loop variable
-                       i,
+                       // Default abort message
+                       strAbort = "canceled",
                        // Fake xhr
                        jqXHR = {
 
@@ -7439,7 +7487,7 @@ jQuery.extend({
 
                                // Cancel the request
                                abort: function( statusText ) {
-                                       statusText = statusText || "abort";
+                                       statusText = statusText || strAbort;
                                        if ( transport ) {
                                                transport.abort( statusText );
                                        }
@@ -7452,6 +7500,8 @@ jQuery.extend({
                // It is defined here because jslint complains if it is declared
                // at the end of the function (which would be more logical and readable)
                function done( status, nativeStatusText, responses, headers ) {
+                       var isSuccess, success, error, response, modified,
+                               statusText = nativeStatusText;
 
                        // Called once
                        if ( state === 2 ) {
@@ -7476,13 +7526,10 @@ jQuery.extend({
                        // Set readyState
                        jqXHR.readyState = status > 0 ? 4 : 0;
 
-                       var isSuccess,
-                               success,
-                               error,
-                               statusText = nativeStatusText,
-                               response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
-                               lastModified,
-                               etag;
+                       // Get response data
+                       if ( responses ) {
+                               response = ajaxHandleResponses( s, jqXHR, responses );
+                       }
 
                        // If successful, handle type chaining
                        if ( status >= 200 && status < 300 || status === 304 ) {
@@ -7490,11 +7537,13 @@ jQuery.extend({
                                // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
                                if ( s.ifModified ) {
 
-                                       if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
-                                               jQuery.lastModified[ ifModifiedKey ] = lastModified;
+                                       modified = jqXHR.getResponseHeader("Last-Modified");
+                                       if ( modified ) {
+                                               jQuery.lastModified[ ifModifiedKey ] = modified;
                                        }
-                                       if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
-                                               jQuery.etag[ ifModifiedKey ] = etag;
+                                       modified = jqXHR.getResponseHeader("Etag");
+                                       if ( modified ) {
+                                               jQuery.etag[ ifModifiedKey ] = modified;
                                        }
                                }
 
@@ -7507,15 +7556,11 @@ jQuery.extend({
                                // If we have data
                                } else {
 
-                                       try {
-                                               success = ajaxConvert( s, response );
-                                               statusText = "success";
-                                               isSuccess = true;
-                                       } catch(e) {
-                                               // We have a parsererror
-                                               statusText = "parsererror";
-                                               error = e;
-                                       }
+                                       isSuccess = ajaxConvert( s, response );
+                                       statusText = isSuccess.state;
+                                       success = isSuccess.data;
+                                       error = isSuccess.error;
+                                       isSuccess = !error;
                                }
                        } else {
                                // We extract error from statusText
@@ -7577,7 +7622,7 @@ jQuery.extend({
                                        }
                                } else {
                                        tmp = map[ jqXHR.status ];
-                                       jqXHR.then( tmp, tmp );
+                                       jqXHR.always( tmp );
                                }
                        }
                        return this;
@@ -7589,7 +7634,7 @@ jQuery.extend({
                s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
 
                // Extract dataTypes list
-               s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+               s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
 
                // Determine if a cross-domain request is in order
                if ( s.crossDomain == null ) {
@@ -7611,7 +7656,7 @@ jQuery.extend({
 
                // If request was aborted inside a prefilter, stop there
                if ( state === 2 ) {
-                       return false;
+                       return jqXHR;
                }
 
                // We can fire global events as of now if asked to
@@ -7684,12 +7729,14 @@ jQuery.extend({
 
                // Allow custom headers/mimetypes and early abort
                if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
-                               // Abort if not done already
-                               jqXHR.abort();
-                               return false;
+                               // Abort if not done already and return
+                               return jqXHR.abort();
 
                }
 
+               // aborting is no longer a cancellation
+               strAbort = "abort";
+
                // Install callbacks on deferreds
                for ( i in { success: 1, error: 1, complete: 1 } ) {
                        jqXHR[ i ]( s[ i ] );
@@ -7728,79 +7775,8 @@ jQuery.extend({
                        }
                }
 
-               return jqXHR;
-       },
-
-       // Serialize an array of form elements or a set of
-       // key/values into a query string
-       param: function( a, traditional ) {
-               var s = [],
-                       add = function( key, value ) {
-                               // If value is a function, invoke it and return its value
-                               value = jQuery.isFunction( value ) ? value() : value;
-                               s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
-                       };
-
-               // Set traditional to true for jQuery <= 1.3.2 behavior.
-               if ( traditional === undefined ) {
-                       traditional = jQuery.ajaxSettings.traditional;
-               }
-
-               // If an array was passed in, assume that it is an array of form elements.
-               if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
-                       // Serialize the form elements
-                       jQuery.each( a, function() {
-                               add( this.name, this.value );
-                       });
-
-               } else {
-                       // If traditional, encode the "old" way (the way 1.3.2 or older
-                       // did it), otherwise encode params recursively.
-                       for ( var prefix in a ) {
-                               buildParams( prefix, a[ prefix ], traditional, add );
-                       }
-               }
-
-               // Return the resulting serialization
-               return s.join( "&" ).replace( r20, "+" );
-       }
-});
-
-function buildParams( prefix, obj, traditional, add ) {
-       if ( jQuery.isArray( obj ) ) {
-               // Serialize array item.
-               jQuery.each( obj, function( i, v ) {
-                       if ( traditional || rbracket.test( prefix ) ) {
-                               // Treat each array item as a scalar.
-                               add( prefix, v );
-
-                       } else {
-                               // If array item is non-scalar (array or object), encode its
-                               // numeric index to resolve deserialization ambiguity issues.
-                               // Note that rack (as of 1.0.0) can't currently deserialize
-                               // nested arrays properly, and attempting to do so may cause
-                               // a server error. Possible fixes are to modify rack's
-                               // deserialization algorithm or to provide an option or flag
-                               // to force array serialization to be shallow.
-                               buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
-                       }
-               });
-
-       } else if ( !traditional && jQuery.type( obj ) === "object" ) {
-               // Serialize object item.
-               for ( var name in obj ) {
-                       buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
-               }
-
-       } else {
-               // Serialize scalar item.
-               add( prefix, obj );
-       }
-}
-
-// This is still on the jQuery object... for now
-// Want to move this to jQuery.ajax some day
-jQuery.extend({
+               return jqXHR;
+       },
 
        // Counter for holding the number of active queries
        active: 0,
@@ -7818,13 +7794,10 @@ jQuery.extend({
  */
 function ajaxHandleResponses( s, jqXHR, responses ) {
 
-       var contents = s.contents,
+       var ct, type, finalDataType, firstDataType,
+               contents = s.contents,
                dataTypes = s.dataTypes,
-               responseFields = s.responseFields,
-               ct,
-               type,
-               finalDataType,
-               firstDataType;
+               responseFields = s.responseFields;
 
        // Fill responseXXX fields
        for ( type in responseFields ) {
@@ -7883,154 +7856,137 @@ function ajaxHandleResponses( s, jqXHR, responses ) {
 // Chain conversions given the request and the original response
 function ajaxConvert( s, response ) {
 
+       var conv, conv2, current, tmp,
+               // Work with a copy of dataTypes in case we need to modify it for conversion
+               dataTypes = s.dataTypes.slice(),
+               prev = dataTypes[ 0 ],
+               converters = {},
+               i = 0;
+
        // Apply the dataFilter if provided
        if ( s.dataFilter ) {
                response = s.dataFilter( response, s.dataType );
        }
 
-       var dataTypes = s.dataTypes,
-               converters = {},
-               i,
-               key,
-               length = dataTypes.length,
-               tmp,
-               // Current and previous dataTypes
-               current = dataTypes[ 0 ],
-               prev,
-               // Conversion expression
-               conversion,
-               // Conversion function
-               conv,
-               // Conversion functions (transitive conversion)
-               conv1,
-               conv2;
-
-       // For each dataType in the chain
-       for ( i = 1; i < length; i++ ) {
-
-               // Create converters map
-               // with lowercased keys
-               if ( i === 1 ) {
-                       for ( key in s.converters ) {
-                               if ( typeof key === "string" ) {
-                                       converters[ key.toLowerCase() ] = s.converters[ key ];
-                               }
-                       }
+       // Create converters map with lowercased keys
+       if ( dataTypes[ 1 ] ) {
+               for ( conv in s.converters ) {
+                       converters[ conv.toLowerCase() ] = s.converters[ conv ];
                }
+       }
 
-               // Get the dataTypes
-               prev = current;
-               current = dataTypes[ i ];
-
-               // If current is auto dataType, update it to prev
-               if ( current === "*" ) {
-                       current = prev;
-               // If no auto and dataTypes are actually different
-               } else if ( prev !== "*" && prev !== current ) {
-
-                       // Get the converter
-                       conversion = prev + " " + current;
-                       conv = converters[ conversion ] || converters[ "* " + current ];
-
-                       // If there is no direct converter, search transitively
-                       if ( !conv ) {
-                               conv2 = undefined;
-                               for ( conv1 in converters ) {
-                                       tmp = conv1.split( " " );
-                                       if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
-                                               conv2 = converters[ tmp[1] + " " + current ];
-                                               if ( conv2 ) {
-                                                       conv1 = converters[ conv1 ];
-                                                       if ( conv1 === true ) {
-                                                               conv = conv2;
-                                                       } else if ( conv2 === true ) {
-                                                               conv = conv1;
+       // Convert to each sequential dataType, tolerating list modification
+       for ( ; (current = dataTypes[++i]); ) {
+
+               // There's only work to do if current dataType is non-auto
+               if ( current !== "*" ) {
+
+                       // Convert response if prev dataType is non-auto and differs from current
+                       if ( prev !== "*" && prev !== current ) {
+
+                               // Seek a direct converter
+                               conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+                               // If none found, seek a pair
+                               if ( !conv ) {
+                                       for ( conv2 in converters ) {
+
+                                               // If conv2 outputs current
+                                               tmp = conv2.split(" ");
+                                               if ( tmp[ 1 ] === current ) {
+
+                                                       // If prev can be converted to accepted input
+                                                       conv = converters[ prev + " " + tmp[ 0 ] ] ||
+                                                               converters[ "* " + tmp[ 0 ] ];
+                                                       if ( conv ) {
+                                                               // Condense equivalence converters
+                                                               if ( conv === true ) {
+                                                                       conv = converters[ conv2 ];
+
+                                                               // Otherwise, insert the intermediate dataType
+                                                               } else if ( converters[ conv2 ] !== true ) {
+                                                                       current = tmp[ 0 ];
+                                                                       dataTypes.splice( i--, 0, current );
+                                                               }
+
+                                                               break;
                                                        }
-                                                       break;
                                                }
                                        }
                                }
-                       }
-                       // If we found no converter, dispatch an error
-                       if ( !( conv || conv2 ) ) {
-                               jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
-                       }
-                       // If found converter is not an equivalence
-                       if ( conv !== true ) {
-                               // Convert with 1 or 2 converters accordingly
-                               response = conv ? conv( response ) : conv2( conv1(response) );
-                       }
-               }
-       }
-       return response;
-}
 
+                               // Apply converter (if not an equivalence)
+                               if ( conv !== true ) {
 
+                                       // Unless errors are allowed to bubble, catch and return them
+                                       if ( conv && s["throws"] ) {
+                                               response = conv( response );
+                                       } else {
+                                               try {
+                                                       response = conv( response );
+                                               } catch ( e ) {
+                                                       return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+                                               }
+                                       }
+                               }
+                       }
 
+                       // Update prev for next iteration
+                       prev = current;
+               }
+       }
 
-var jsc = jQuery.now(),
-       jsre = /(\=)\?(&|$)|\?\?/i;
+       return { state: "success", data: response };
+}
+var oldCallbacks = [],
+       rquestion = /\?/,
+       rjsonp = /(=)\?(?=&|$)|\?\?/,
+       nonce = jQuery.now();
 
 // Default jsonp settings
 jQuery.ajaxSetup({
        jsonp: "callback",
        jsonpCallback: function() {
-               return jQuery.expando + "_" + ( jsc++ );
+               var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+               this[ callback ] = true;
+               return callback;
        }
 });
 
 // Detect, normalize options and install callbacks for jsonp requests
 jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
 
-       var inspectData = ( typeof s.data === "string" ) && /^application\/x\-www\-form\-urlencoded/.test( s.contentType );
-
-       if ( s.dataTypes[ 0 ] === "jsonp" ||
-               s.jsonp !== false && ( jsre.test( s.url ) ||
-                               inspectData && jsre.test( s.data ) ) ) {
-
-               var responseContainer,
-                       jsonpCallback = s.jsonpCallback =
-                               jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
-                       previous = window[ jsonpCallback ],
-                       url = s.url,
-                       data = s.data,
-                       replace = "$1" + jsonpCallback + "$2";
-
-               if ( s.jsonp !== false ) {
-                       url = url.replace( jsre, replace );
-                       if ( s.url === url ) {
-                               if ( inspectData ) {
-                                       data = data.replace( jsre, replace );
-                               }
-                               if ( s.data === data ) {
-                                       // Add callback manually
-                                       url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
-                               }
-                       }
+       var callbackName, overwritten, responseContainer,
+               data = s.data,
+               url = s.url,
+               hasCallback = s.jsonp !== false,
+               replaceInUrl = hasCallback && rjsonp.test( url ),
+               replaceInData = hasCallback && !replaceInUrl && typeof data === "string" &&
+                       !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") &&
+                       rjsonp.test( data );
+
+       // Handle iff the expected data type is "jsonp" or we have a parameter to set
+       if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) {
+
+               // Get callback name, remembering preexisting value associated with it
+               callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+                       s.jsonpCallback() :
+                       s.jsonpCallback;
+               overwritten = window[ callbackName ];
+
+               // Insert callback into url or form data
+               if ( replaceInUrl ) {
+                       s.url = url.replace( rjsonp, "$1" + callbackName );
+               } else if ( replaceInData ) {
+                       s.data = data.replace( rjsonp, "$1" + callbackName );
+               } else if ( hasCallback ) {
+                       s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
                }
 
-               s.url = url;
-               s.data = data;
-
-               // Install callback
-               window[ jsonpCallback ] = function( response ) {
-                       responseContainer = [ response ];
-               };
-
-               // Clean-up function
-               jqXHR.always(function() {
-                       // Set callback back to previous value
-                       window[ jsonpCallback ] = previous;
-                       // Call if it was a function and we have a response
-                       if ( responseContainer && jQuery.isFunction( previous ) ) {
-                               window[ jsonpCallback ]( responseContainer[ 0 ] );
-                       }
-               });
-
                // Use data converter to retrieve json after script execution
                s.converters["script json"] = function() {
                        if ( !responseContainer ) {
-                               jQuery.error( jsonpCallback + " was not called" );
+                               jQuery.error( callbackName + " was not called" );
                        }
                        return responseContainer[ 0 ];
                };
@@ -8038,14 +7994,37 @@ jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
                // force json dataType
                s.dataTypes[ 0 ] = "json";
 
-               // Delegate to script
-               return "script";
-       }
-});
+               // Install callback
+               window[ callbackName ] = function() {
+                       responseContainer = arguments;
+               };
+
+               // Clean-up function (fires after converters)
+               jqXHR.always(function() {
+                       // Restore preexisting value
+                       window[ callbackName ] = overwritten;
+
+                       // Save back as free
+                       if ( s[ callbackName ] ) {
+                               // make sure that re-using the options doesn't screw things around
+                               s.jsonpCallback = originalSettings.jsonpCallback;
 
+                               // save the callback name for future use
+                               oldCallbacks.push( callbackName );
+                       }
 
+                       // Call if it was a function and we have a response
+                       if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+                               overwritten( responseContainer[ 0 ] );
+                       }
 
+                       responseContainer = overwritten = undefined;
+               });
 
+               // Delegate to script
+               return "script";
+       }
+});
 // Install script dataType
 jQuery.ajaxSetup({
        accepts: {
@@ -8131,19 +8110,15 @@ jQuery.ajaxTransport( "script", function(s) {
                };
        }
 });
-
-
-
-
-var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+var xhrCallbacks,
+       // #5280: Internet Explorer will keep connections alive if we don't abort on unload
        xhrOnUnloadAbort = window.ActiveXObject ? function() {
                // Abort all pending requests
                for ( var key in xhrCallbacks ) {
                        xhrCallbacks[ key ]( 0, 1 );
                }
        } : false,
-       xhrId = 0,
-       xhrCallbacks;
+       xhrId = 0;
 
 // Functions to create xhrs
 function createStandardXHR() {
@@ -8194,9 +8169,8 @@ if ( jQuery.support.ajax ) {
                                send: function( headers, complete ) {
 
                                        // Get a new xhr
-                                       var xhr = s.xhr(),
-                                               handle,
-                                               i;
+                                       var handle, i,
+                                               xhr = s.xhr();
 
                                        // Open the socket
                                        // Passing null username, generates a login popup on Opera (#2865)
@@ -8249,7 +8223,7 @@ if ( jQuery.support.ajax ) {
                                                        xml;
 
                                                // Firefox throws exceptions when accessing properties
-                                               // of an xhr when a network error occured
+                                               // of an xhr when a network error occurred
                                                // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
                                                try {
 
@@ -8325,11 +8299,13 @@ if ( jQuery.support.ajax ) {
                                                }
                                        };
 
-                                       // if we're in sync mode or it's in cache
-                                       // and has been retrieved directly (IE6 & IE7)
-                                       // we need to manually fire the callback
-                                       if ( !s.async || xhr.readyState === 4 ) {
+                                       if ( !s.async ) {
+                                               // if we're in sync mode we fire the callback
                                                callback();
+                                       } else if ( xhr.readyState === 4 ) {
+                                               // (IE6 & IE7) if it's in cache and has been
+                                               // retrieved directly we need to fire the callback
+                                               setTimeout( callback, 0 );
                                        } else {
                                                handle = ++xhrId;
                                                if ( xhrOnUnloadAbort ) {
@@ -8353,793 +8329,662 @@ if ( jQuery.support.ajax ) {
                                }
                        };
                }
-       });
-}
-
-
-
-
-var elemdisplay = {},
-       iframe, iframeDoc,
-       rfxtypes = /^(?:toggle|show|hide)$/,
-       rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
-       timerId,
-       fxAttrs = [
-               // height animations
-               [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
-               // width animations
-               [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
-               // opacity animations
-               [ "opacity" ]
-       ],
-       fxNow;
-
-jQuery.fn.extend({
-       show: function( speed, easing, callback ) {
-               var elem, display;
-
-               if ( speed || speed === 0 ) {
-                       return this.animate( genFx("show", 3), speed, easing, callback );
-
-               } else {
-                       for ( var i = 0, j = this.length; i < j; i++ ) {
-                               elem = this[ i ];
-
-                               if ( elem.style ) {
-                                       display = elem.style.display;
-
-                                       // Reset the inline display of this element to learn if it is
-                                       // being hidden by cascaded rules or not
-                                       if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
-                                               display = elem.style.display = "";
-                                       }
-
-                                       // Set elements which have been overridden with display: none
-                                       // in a stylesheet to whatever the default browser style is
-                                       // for such an element
-                                       if ( (display === "" && jQuery.css(elem, "display") === "none") ||
-                                               !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
-                                               jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
-                                       }
-                               }
-                       }
-
-                       // Set the display of most of the elements in a second loop
-                       // to avoid the constant reflow
-                       for ( i = 0; i < j; i++ ) {
-                               elem = this[ i ];
-
-                               if ( elem.style ) {
-                                       display = elem.style.display;
-
-                                       if ( display === "" || display === "none" ) {
-                                               elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
-                                       }
-                               }
-                       }
-
-                       return this;
-               }
-       },
-
-       hide: function( speed, easing, callback ) {
-               if ( speed || speed === 0 ) {
-                       return this.animate( genFx("hide", 3), speed, easing, callback);
-
-               } else {
-                       var elem, display,
-                               i = 0,
-                               j = this.length;
-
-                       for ( ; i < j; i++ ) {
-                               elem = this[i];
-                               if ( elem.style ) {
-                                       display = jQuery.css( elem, "display" );
-
-                                       if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
-                                               jQuery._data( elem, "olddisplay", display );
-                                       }
-                               }
-                       }
-
-                       // Set the display of the elements in a second loop
-                       // to avoid the constant reflow
-                       for ( i = 0; i < j; i++ ) {
-                               if ( this[i].style ) {
-                                       this[i].style.display = "none";
-                               }
-                       }
-
-                       return this;
-               }
-       },
-
-       // Save the old toggle function
-       _toggle: jQuery.fn.toggle,
-
-       toggle: function( fn, fn2, callback ) {
-               var bool = typeof fn === "boolean";
-
-               if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
-                       this._toggle.apply( this, arguments );
-
-               } else if ( fn == null || bool ) {
-                       this.each(function() {
-                               var state = bool ? fn : jQuery(this).is(":hidden");
-                               jQuery(this)[ state ? "show" : "hide" ]();
-                       });
-
-               } else {
-                       this.animate(genFx("toggle", 3), fn, fn2, callback);
-               }
-
-               return this;
-       },
-
-       fadeTo: function( speed, to, easing, callback ) {
-               return this.filter(":hidden").css("opacity", 0).show().end()
-                                       .animate({opacity: to}, speed, easing, callback);
-       },
-
-       animate: function( prop, speed, easing, callback ) {
-               var optall = jQuery.speed( speed, easing, callback );
-
-               if ( jQuery.isEmptyObject( prop ) ) {
-                       return this.each( optall.complete, [ false ] );
-               }
-
-               // Do not change referenced properties as per-property easing will be lost
-               prop = jQuery.extend( {}, prop );
-
-               function doAnimation() {
-                       // XXX 'this' does not always have a nodeName when running the
-                       // test suite
-
-                       if ( optall.queue === false ) {
-                               jQuery._mark( this );
-                       }
-
-                       var opt = jQuery.extend( {}, optall ),
-                               isElement = this.nodeType === 1,
-                               hidden = isElement && jQuery(this).is(":hidden"),
-                               name, val, p, e, hooks, replace,
-                               parts, start, end, unit,
-                               method;
-
-                       // will store per property easing and be used to determine when an animation is complete
-                       opt.animatedProperties = {};
-
-                       // first pass over propertys to expand / normalize
-                       for ( p in prop ) {
-                               name = jQuery.camelCase( p );
-                               if ( p !== name ) {
-                                       prop[ name ] = prop[ p ];
-                                       delete prop[ p ];
-                               }
-
-                               if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
-                                       replace = hooks.expand( prop[ name ] );
-                                       delete prop[ name ];
-
-                                       // not quite $.extend, this wont overwrite keys already present.
-                                       // also - reusing 'p' from above because we have the correct "name"
-                                       for ( p in replace ) {
-                                               if ( ! ( p in prop ) ) {
-                                                       prop[ p ] = replace[ p ];
-                                               }
-                                       }
-                               }
-                       }
-
-                       for ( name in prop ) {
-                               val = prop[ name ];
-                               // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
-                               if ( jQuery.isArray( val ) ) {
-                                       opt.animatedProperties[ name ] = val[ 1 ];
-                                       val = prop[ name ] = val[ 0 ];
-                               } else {
-                                       opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
-                               }
-
-                               if ( val === "hide" && hidden || val === "show" && !hidden ) {
-                                       return opt.complete.call( this );
-                               }
-
-                               if ( isElement && ( name === "height" || name === "width" ) ) {
-                                       // Make sure that nothing sneaks out
-                                       // Record all 3 overflow attributes because IE does not
-                                       // change the overflow attribute when overflowX and
-                                       // overflowY are set to the same value
-                                       opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
-
-                                       // Set display property to inline-block for height/width
-                                       // animations on inline elements that are having width/height animated
-                                       if ( jQuery.css( this, "display" ) === "inline" &&
-                                                       jQuery.css( this, "float" ) === "none" ) {
-
-                                               // inline-level elements accept inline-block;
-                                               // block-level elements need to be inline with layout
-                                               if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
-                                                       this.style.display = "inline-block";
-
-                                               } else {
-                                                       this.style.zoom = 1;
-                                               }
-                                       }
-                               }
-                       }
-
-                       if ( opt.overflow != null ) {
-                               this.style.overflow = "hidden";
-                       }
-
-                       for ( p in prop ) {
-                               e = new jQuery.fx( this, opt, p );
-                               val = prop[ p ];
-
-                               if ( rfxtypes.test( val ) ) {
-
-                                       // Tracks whether to show or hide based on private
-                                       // data attached to the element
-                                       method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
-                                       if ( method ) {
-                                               jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
-                                               e[ method ]();
-                                       } else {
-                                               e[ val ]();
-                                       }
-
-                               } else {
-                                       parts = rfxnum.exec( val );
-                                       start = e.cur();
-
-                                       if ( parts ) {
-                                               end = parseFloat( parts[2] );
-                                               unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
-
-                                               // We need to compute starting value
-                                               if ( unit !== "px" ) {
-                                                       jQuery.style( this, p, (end || 1) + unit);
-                                                       start = ( (end || 1) / e.cur() ) * start;
-                                                       jQuery.style( this, p, start + unit);
-                                               }
-
-                                               // If a +=/-= token was provided, we're doing a relative animation
-                                               if ( parts[1] ) {
-                                                       end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
-                                               }
-
-                                               e.custom( start, end, unit );
-
-                                       } else {
-                                               e.custom( start, val, "" );
-                                       }
-                               }
-                       }
-
-                       // For JS strict compliance
-                       return true;
-               }
-
-               return optall.queue === false ?
-                       this.each( doAnimation ) :
-                       this.queue( optall.queue, doAnimation );
-       },
-
-       stop: function( type, clearQueue, gotoEnd ) {
-               if ( typeof type !== "string" ) {
-                       gotoEnd = clearQueue;
-                       clearQueue = type;
-                       type = undefined;
-               }
-               if ( clearQueue && type !== false ) {
-                       this.queue( type || "fx", [] );
-               }
-
-               return this.each(function() {
-                       var index,
-                               hadTimers = false,
-                               timers = jQuery.timers,
-                               data = jQuery._data( this );
-
-                       // clear marker counters if we know they won't be
-                       if ( !gotoEnd ) {
-                               jQuery._unmark( true, this );
-                       }
-
-                       function stopQueue( elem, data, index ) {
-                               var hooks = data[ index ];
-                               jQuery.removeData( elem, index, true );
-                               hooks.stop( gotoEnd );
-                       }
-
-                       if ( type == null ) {
-                               for ( index in data ) {
-                                       if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
-                                               stopQueue( this, data, index );
-                                       }
-                               }
-                       } else if ( data[ index = type + ".run" ] && data[ index ].stop ){
-                               stopQueue( this, data, index );
-                       }
-
-                       for ( index = timers.length; index--; ) {
-                               if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
-                                       if ( gotoEnd ) {
-
-                                               // force the next step to be the last
-                                               timers[ index ]( true );
-                                       } else {
-                                               timers[ index ].saveState();
-                                       }
-                                       hadTimers = true;
-                                       timers.splice( index, 1 );
-                               }
-                       }
-
-                       // start the next in the queue if the last step wasn't forced
-                       // timers currently will call their complete callbacks, which will dequeue
-                       // but only if they were gotoEnd
-                       if ( !( gotoEnd && hadTimers ) ) {
-                               jQuery.dequeue( this, type );
-                       }
-               });
-       }
-
-});
-
-// Animations created synchronously will run synchronously
-function createFxNow() {
-       setTimeout( clearFxNow, 0 );
-       return ( fxNow = jQuery.now() );
-}
-
-function clearFxNow() {
-       fxNow = undefined;
-}
-
-// Generate parameters to create a standard animation
-function genFx( type, num ) {
-       var obj = {};
-
-       jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
-               obj[ this ] = type;
-       });
-
-       return obj;
-}
-
-// Generate shortcuts for custom animations
-jQuery.each({
-       slideDown: genFx( "show", 1 ),
-       slideUp: genFx( "hide", 1 ),
-       slideToggle: genFx( "toggle", 1 ),
-       fadeIn: { opacity: "show" },
-       fadeOut: { opacity: "hide" },
-       fadeToggle: { opacity: "toggle" }
-}, function( name, props ) {
-       jQuery.fn[ name ] = function( speed, easing, callback ) {
-               return this.animate( props, speed, easing, callback );
+       });
+}
+var fxNow, timerId,
+       rfxtypes = /^(?:toggle|show|hide)$/,
+       rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+       rrun = /queueHooks$/,
+       animationPrefilters = [ defaultPrefilter ],
+       tweeners = {
+               "*": [function( prop, value ) {
+                       var end, unit, prevScale,
+                               tween = this.createTween( prop, value ),
+                               parts = rfxnum.exec( value ),
+                               target = tween.cur(),
+                               start = +target || 0,
+                               scale = 1;
+
+                       if ( parts ) {
+                               end = +parts[2];
+                               unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+                               // We need to compute starting value
+                               if ( unit !== "px" && start ) {
+                                       // Iteratively approximate from a nonzero starting point
+                                       // Prefer the current property, because this process will be trivial if it uses the same units
+                                       // Fallback to end or a simple constant
+                                       start = jQuery.css( tween.elem, prop, true ) || end || 1;
+
+                                       do {
+                                               // If previous iteration zeroed out, double until we get *something*
+                                               // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+                                               prevScale = scale = scale || ".5";
+
+                                               // Adjust and apply
+                                               start = start / scale;
+                                               jQuery.style( tween.elem, prop, start + unit );
+
+                                               // Update scale, tolerating zeroes from tween.cur()
+                                               scale = tween.cur() / target;
+
+                                       // Stop looping if we've hit the mark or scale is unchanged
+                                       } while ( scale !== 1 && scale !== prevScale );
+                               }
+
+                               tween.unit = unit;
+                               tween.start = start;
+                               // If a +=/-= token was provided, we're doing a relative animation
+                               tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
+                       }
+                       return tween;
+               }]
        };
-});
 
-jQuery.extend({
-       speed: function( speed, easing, fn ) {
-               var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
-                       complete: fn || !fn && easing ||
-                               jQuery.isFunction( speed ) && speed,
-                       duration: speed,
-                       easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
-               };
+// Animations created synchronously will run synchronously
+function createFxNow() {
+       setTimeout(function() {
+               fxNow = undefined;
+       }, 0 );
+       return ( fxNow = jQuery.now() );
+}
 
-               opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
-                       opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+function createTweens( animation, props ) {
+       jQuery.each( props, function( prop, value ) {
+               var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+                       index = 0,
+                       length = collection.length;
+               for ( ; index < length; index++ ) {
+                       if ( collection[ index ].call( animation, prop, value ) ) {
 
-               // normalize opt.queue - true/undefined/null -> "fx"
-               if ( opt.queue == null || opt.queue === true ) {
-                       opt.queue = "fx";
+                               // we're done with this property
+                               return;
+                       }
                }
+       });
+}
 
-               // Queueing
-               opt.old = opt.complete;
-
-               opt.complete = function( noUnmark ) {
-                       if ( jQuery.isFunction( opt.old ) ) {
-                               opt.old.call( this );
+function Animation( elem, properties, options ) {
+       var result,
+               index = 0,
+               tweenerIndex = 0,
+               length = animationPrefilters.length,
+               deferred = jQuery.Deferred().always( function() {
+                       // don't match elem in the :animated selector
+                       delete tick.elem;
+               }),
+               tick = function() {
+                       var currentTime = fxNow || createFxNow(),
+                               remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+                               percent = 1 - ( remaining / animation.duration || 0 ),
+                               index = 0,
+                               length = animation.tweens.length;
+
+                       for ( ; index < length ; index++ ) {
+                               animation.tweens[ index ].run( percent );
+                       }
+
+                       deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+                       if ( percent < 1 && length ) {
+                               return remaining;
+                       } else {
+                               deferred.resolveWith( elem, [ animation ] );
+                               return false;
                        }
+               },
+               animation = deferred.promise({
+                       elem: elem,
+                       props: jQuery.extend( {}, properties ),
+                       opts: jQuery.extend( true, { specialEasing: {} }, options ),
+                       originalProperties: properties,
+                       originalOptions: options,
+                       startTime: fxNow || createFxNow(),
+                       duration: options.duration,
+                       tweens: [],
+                       createTween: function( prop, end, easing ) {
+                               var tween = jQuery.Tween( elem, animation.opts, prop, end,
+                                               animation.opts.specialEasing[ prop ] || animation.opts.easing );
+                               animation.tweens.push( tween );
+                               return tween;
+                       },
+                       stop: function( gotoEnd ) {
+                               var index = 0,
+                                       // if we are going to the end, we want to run all the tweens
+                                       // otherwise we skip this part
+                                       length = gotoEnd ? animation.tweens.length : 0;
+
+                               for ( ; index < length ; index++ ) {
+                                       animation.tweens[ index ].run( 1 );
+                               }
 
-                       if ( opt.queue ) {
-                               jQuery.dequeue( this, opt.queue );
-                       } else if ( noUnmark !== false ) {
-                               jQuery._unmark( this );
+                               // resolve when we played the last frame
+                               // otherwise, reject
+                               if ( gotoEnd ) {
+                                       deferred.resolveWith( elem, [ animation, gotoEnd ] );
+                               } else {
+                                       deferred.rejectWith( elem, [ animation, gotoEnd ] );
+                               }
+                               return this;
                        }
-               };
+               }),
+               props = animation.props;
 
-               return opt;
-       },
+       propFilter( props, animation.opts.specialEasing );
 
-       easing: {
-               linear: function( p ) {
-                       return p;
-               },
-               swing: function( p ) {
-                       return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5;
+       for ( ; index < length ; index++ ) {
+               result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+               if ( result ) {
+                       return result;
                }
-       },
-
-       timers: [],
+       }
 
-       fx: function( elem, options, prop ) {
-               this.options = options;
-               this.elem = elem;
-               this.prop = prop;
+       createTweens( animation, props );
 
-               options.orig = options.orig || {};
+       if ( jQuery.isFunction( animation.opts.start ) ) {
+               animation.opts.start.call( elem, animation );
        }
 
-});
+       jQuery.fx.timer(
+               jQuery.extend( tick, {
+                       anim: animation,
+                       queue: animation.opts.queue,
+                       elem: elem
+               })
+       );
 
-jQuery.fx.prototype = {
-       // Simple function for setting a style value
-       update: function() {
-               if ( this.options.step ) {
-                       this.options.step.call( this.elem, this.now, this );
-               }
+       // attach callbacks from options
+       return animation.progress( animation.opts.progress )
+               .done( animation.opts.done, animation.opts.complete )
+               .fail( animation.opts.fail )
+               .always( animation.opts.always );
+}
 
-               ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
-       },
+function propFilter( props, specialEasing ) {
+       var index, name, easing, value, hooks;
 
-       // Get the current size
-       cur: function() {
-               if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
-                       return this.elem[ this.prop ];
+       // camelCase, specialEasing and expand cssHook pass
+       for ( index in props ) {
+               name = jQuery.camelCase( index );
+               easing = specialEasing[ name ];
+               value = props[ index ];
+               if ( jQuery.isArray( value ) ) {
+                       easing = value[ 1 ];
+                       value = props[ index ] = value[ 0 ];
                }
 
-               var parsed,
-                       r = jQuery.css( this.elem, this.prop );
-               // Empty strings, null, undefined and "auto" are converted to 0,
-               // complex values such as "rotate(1rad)" are returned as is,
-               // simple values such as "10px" are parsed to Float.
-               return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
-       },
-
-       // Start an animation from one number to another
-       custom: function( from, to, unit ) {
-               var self = this,
-                       fx = jQuery.fx;
-
-               this.startTime = fxNow || createFxNow();
-               this.end = to;
-               this.now = this.start = from;
-               this.pos = this.state = 0;
-               this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
-
-               function t( gotoEnd ) {
-                       return self.step( gotoEnd );
+               if ( index !== name ) {
+                       props[ name ] = value;
+                       delete props[ index ];
                }
 
-               t.queue = this.options.queue;
-               t.elem = this.elem;
-               t.saveState = function() {
-                       if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
-                               if ( self.options.hide ) {
-                                       jQuery._data( self.elem, "fxshow" + self.prop, self.start );
-                               } else if ( self.options.show ) {
-                                       jQuery._data( self.elem, "fxshow" + self.prop, self.end );
+               hooks = jQuery.cssHooks[ name ];
+               if ( hooks && "expand" in hooks ) {
+                       value = hooks.expand( value );
+                       delete props[ name ];
+
+                       // not quite $.extend, this wont overwrite keys already present.
+                       // also - reusing 'index' from above because we have the correct "name"
+                       for ( index in value ) {
+                               if ( !( index in props ) ) {
+                                       props[ index ] = value[ index ];
+                                       specialEasing[ index ] = easing;
                                }
                        }
-               };
-
-               if ( t() && jQuery.timers.push(t) && !timerId ) {
-                       timerId = setInterval( fx.tick, fx.interval );
+               } else {
+                       specialEasing[ name ] = easing;
                }
-       },
-
-       // Simple 'show' function
-       show: function() {
-               var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+       }
+}
 
-               // Remember where we started, so that we can go back to it later
-               this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
-               this.options.show = true;
+jQuery.Animation = jQuery.extend( Animation, {
 
-               // Begin the animation
-               // Make sure that we start at a small width/height to avoid any flash of content
-               if ( dataShow !== undefined ) {
-                       // This show is picking up where a previous hide or show left off
-                       this.custom( this.cur(), dataShow );
+       tweener: function( props, callback ) {
+               if ( jQuery.isFunction( props ) ) {
+                       callback = props;
+                       props = [ "*" ];
                } else {
-                       this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+                       props = props.split(" ");
                }
 
-               // Start by showing the element
-               jQuery( this.elem ).show();
-       },
-
-       // Simple 'hide' function
-       hide: function() {
-               // Remember where we started, so that we can go back to it later
-               this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
-               this.options.hide = true;
+               var prop,
+                       index = 0,
+                       length = props.length;
 
-               // Begin the animation
-               this.custom( this.cur(), 0 );
+               for ( ; index < length ; index++ ) {
+                       prop = props[ index ];
+                       tweeners[ prop ] = tweeners[ prop ] || [];
+                       tweeners[ prop ].unshift( callback );
+               }
        },
 
-       // Each step of an animation
-       step: function( gotoEnd ) {
-               var p, n, complete,
-                       t = fxNow || createFxNow(),
-                       done = true,
-                       elem = this.elem,
-                       options = this.options;
-
-               if ( gotoEnd || t >= options.duration + this.startTime ) {
-                       this.now = this.end;
-                       this.pos = this.state = 1;
-                       this.update();
+       prefilter: function( callback, prepend ) {
+               if ( prepend ) {
+                       animationPrefilters.unshift( callback );
+               } else {
+                       animationPrefilters.push( callback );
+               }
+       }
+});
 
-                       options.animatedProperties[ this.prop ] = true;
+function defaultPrefilter( elem, props, opts ) {
+       var index, prop, value, length, dataShow, tween, hooks, oldfire,
+               anim = this,
+               style = elem.style,
+               orig = {},
+               handled = [],
+               hidden = elem.nodeType && isHidden( elem );
+
+       // handle queue: false promises
+       if ( !opts.queue ) {
+               hooks = jQuery._queueHooks( elem, "fx" );
+               if ( hooks.unqueued == null ) {
+                       hooks.unqueued = 0;
+                       oldfire = hooks.empty.fire;
+                       hooks.empty.fire = function() {
+                               if ( !hooks.unqueued ) {
+                                       oldfire();
+                               }
+                       };
+               }
+               hooks.unqueued++;
 
-                       for ( p in options.animatedProperties ) {
-                               if ( options.animatedProperties[ p ] !== true ) {
-                                       done = false;
+               anim.always(function() {
+                       // doing this makes sure that the complete handler will be called
+                       // before this completes
+                       anim.always(function() {
+                               hooks.unqueued--;
+                               if ( !jQuery.queue( elem, "fx" ).length ) {
+                                       hooks.empty.fire();
                                }
-                       }
+                       });
+               });
+       }
 
-                       if ( done ) {
-                               // Reset the overflow
-                               if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+       // height/width overflow pass
+       if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+               // Make sure that nothing sneaks out
+               // Record all 3 overflow attributes because IE does not
+               // change the overflow attribute when overflowX and
+               // overflowY are set to the same value
+               opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
 
-                                       jQuery.each( [ "", "X", "Y" ], function( index, value ) {
-                                               elem.style[ "overflow" + value ] = options.overflow[ index ];
-                                       });
-                               }
+               // Set display property to inline-block for height/width
+               // animations on inline elements that are having width/height animated
+               if ( jQuery.css( elem, "display" ) === "inline" &&
+                               jQuery.css( elem, "float" ) === "none" ) {
 
-                               // Hide the element if the "hide" operation was done
-                               if ( options.hide ) {
-                                       jQuery( elem ).hide();
-                               }
+                       // inline-level elements accept inline-block;
+                       // block-level elements need to be inline with layout
+                       if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+                               style.display = "inline-block";
 
-                               // Reset the properties, if the item has been hidden or shown
-                               if ( options.hide || options.show ) {
-                                       for ( p in options.animatedProperties ) {
-                                               jQuery.style( elem, p, options.orig[ p ] );
-                                               jQuery.removeData( elem, "fxshow" + p, true );
-                                               // Toggle data is no longer needed
-                                               jQuery.removeData( elem, "toggle" + p, true );
-                                       }
-                               }
+                       } else {
+                               style.zoom = 1;
+                       }
+               }
+       }
 
-                               // Execute the complete function
-                               // in the event that the complete function throws an exception
-                               // we must ensure it won't be called twice. #5684
+       if ( opts.overflow ) {
+               style.overflow = "hidden";
+               if ( !jQuery.support.shrinkWrapBlocks ) {
+                       anim.done(function() {
+                               style.overflow = opts.overflow[ 0 ];
+                               style.overflowX = opts.overflow[ 1 ];
+                               style.overflowY = opts.overflow[ 2 ];
+                       });
+               }
+       }
 
-                               complete = options.complete;
-                               if ( complete ) {
 
-                                       options.complete = false;
-                                       complete.call( elem );
-                               }
+       // show/hide pass
+       for ( index in props ) {
+               value = props[ index ];
+               if ( rfxtypes.exec( value ) ) {
+                       delete props[ index ];
+                       if ( value === ( hidden ? "hide" : "show" ) ) {
+                               continue;
                        }
+                       handled.push( index );
+               }
+       }
 
-                       return false;
-
+       length = handled.length;
+       if ( length ) {
+               dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+               if ( hidden ) {
+                       jQuery( elem ).show();
                } else {
-                       // classical easing cannot be used with an Infinity duration
-                       if ( options.duration == Infinity ) {
-                               this.now = t;
-                       } else {
-                               n = t - this.startTime;
-                               this.state = n / options.duration;
+                       anim.done(function() {
+                               jQuery( elem ).hide();
+                       });
+               }
+               anim.done(function() {
+                       var prop;
+                       jQuery.removeData( elem, "fxshow", true );
+                       for ( prop in orig ) {
+                               jQuery.style( elem, prop, orig[ prop ] );
+                       }
+               });
+               for ( index = 0 ; index < length ; index++ ) {
+                       prop = handled[ index ];
+                       tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+                       orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
 
-                               // Perform the easing function, defaults to swing
-                               this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
-                               this.now = this.start + ( (this.end - this.start) * this.pos );
+                       if ( !( prop in dataShow ) ) {
+                               dataShow[ prop ] = tween.start;
+                               if ( hidden ) {
+                                       tween.end = tween.start;
+                                       tween.start = prop === "width" || prop === "height" ? 1 : 0;
+                               }
                        }
-                       // Perform the next step of the animation
-                       this.update();
                }
-
-               return true;
        }
-};
+}
 
-jQuery.extend( jQuery.fx, {
-       tick: function() {
-               var timer,
-                       timers = jQuery.timers,
-                       i = 0;
+function Tween( elem, options, prop, end, easing ) {
+       return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
 
-               for ( ; i < timers.length; i++ ) {
-                       timer = timers[ i ];
-                       // Checks the timer has not already been removed
-                       if ( !timer() && timers[ i ] === timer ) {
-                               timers.splice( i--, 1 );
-                       }
+Tween.prototype = {
+       constructor: Tween,
+       init: function( elem, options, prop, end, easing, unit ) {
+               this.elem = elem;
+               this.prop = prop;
+               this.easing = easing || "swing";
+               this.options = options;
+               this.start = this.now = this.cur();
+               this.end = end;
+               this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+       },
+       cur: function() {
+               var hooks = Tween.propHooks[ this.prop ];
+
+               return hooks && hooks.get ?
+                       hooks.get( this ) :
+                       Tween.propHooks._default.get( this );
+       },
+       run: function( percent ) {
+               var eased,
+                       hooks = Tween.propHooks[ this.prop ];
+
+               this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration );
+               this.now = ( this.end - this.start ) * eased + this.start;
+
+               if ( this.options.step ) {
+                       this.options.step.call( this.elem, this.now, this );
                }
 
-               if ( !timers.length ) {
-                       jQuery.fx.stop();
+               if ( hooks && hooks.set ) {
+                       hooks.set( this );
+               } else {
+                       Tween.propHooks._default.set( this );
                }
-       },
+               return this;
+       }
+};
 
-       interval: 13,
+Tween.prototype.init.prototype = Tween.prototype;
 
-       stop: function() {
-               clearInterval( timerId );
-               timerId = null;
-       },
+Tween.propHooks = {
+       _default: {
+               get: function( tween ) {
+                       var result;
 
-       speeds: {
-               slow: 600,
-               fast: 200,
-               // Default speed
-               _default: 400
-       },
+                       if ( tween.elem[ tween.prop ] != null &&
+                               (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+                               return tween.elem[ tween.prop ];
+                       }
 
-       step: {
-               opacity: function( fx ) {
-                       jQuery.style( fx.elem, "opacity", fx.now );
+                       // passing any value as a 4th parameter to .css will automatically
+                       // attempt a parseFloat and fallback to a string if the parse fails
+                       // so, simple values such as "10px" are parsed to Float.
+                       // complex values such as "rotate(1rad)" are returned as is.
+                       result = jQuery.css( tween.elem, tween.prop, false, "" );
+                       // Empty strings, null, undefined and "auto" are converted to 0.
+                       return !result || result === "auto" ? 0 : result;
                },
-
-               _default: function( fx ) {
-                       if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
-                               fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+               set: function( tween ) {
+                       // use step hook for back compat - use cssHook if its there - use .style if its
+                       // available and use plain properties where available
+                       if ( jQuery.fx.step[ tween.prop ] ) {
+                               jQuery.fx.step[ tween.prop ]( tween );
+                       } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+                               jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
                        } else {
-                               fx.elem[ fx.prop ] = fx.now;
+                               tween.elem[ tween.prop ] = tween.now;
                        }
                }
        }
-});
+};
 
-// Ensure props that can't be negative don't go there on undershoot easing
-jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) {
-       // exclude marginTop, marginLeft, marginBottom and marginRight from this list
-       if ( prop.indexOf( "margin" ) ) {
-               jQuery.fx.step[ prop ] = function( fx ) {
-                       jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
-               };
+// Remove in 2.0 - this supports IE8's panic based approach
+// to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+       set: function( tween ) {
+               if ( tween.elem.nodeType && tween.elem.parentNode ) {
+                       tween.elem[ tween.prop ] = tween.now;
+               }
        }
-});
+};
 
-if ( jQuery.expr && jQuery.expr.filters ) {
-       jQuery.expr.filters.animated = function( elem ) {
-               return jQuery.grep(jQuery.timers, function( fn ) {
-                       return elem === fn.elem;
-               }).length;
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+       var cssFn = jQuery.fn[ name ];
+       jQuery.fn[ name ] = function( speed, easing, callback ) {
+               return speed == null || typeof speed === "boolean" ||
+                       // special check for .toggle( handler, handler, ... )
+                       ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?
+                       cssFn.apply( this, arguments ) :
+                       this.animate( genFx( name, true ), speed, easing, callback );
        };
-}
+});
 
-// Try to restore the default display value of an element
-function defaultDisplay( nodeName ) {
+jQuery.fn.extend({
+       fadeTo: function( speed, to, easing, callback ) {
 
-       if ( !elemdisplay[ nodeName ] ) {
+               // show any hidden elements after setting opacity to 0
+               return this.filter( isHidden ).css( "opacity", 0 ).show()
 
-               var body = document.body,
-                       elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
-                       display = elem.css( "display" );
-               elem.remove();
+                       // animate to the value specified
+                       .end().animate({ opacity: to }, speed, easing, callback );
+       },
+       animate: function( prop, speed, easing, callback ) {
+               var empty = jQuery.isEmptyObject( prop ),
+                       optall = jQuery.speed( speed, easing, callback ),
+                       doAnimation = function() {
+                               // Operate on a copy of prop so per-property easing won't be lost
+                               var anim = Animation( this, jQuery.extend( {}, prop ), optall );
 
-               // If the simple way fails,
-               // get element's real default display by attaching it to a temp iframe
-               if ( display === "none" || display === "" ) {
-                       // No iframe to use yet, so create it
-                       if ( !iframe ) {
-                               iframe = document.createElement( "iframe" );
-                               iframe.frameBorder = iframe.width = iframe.height = 0;
-                       }
+                               // Empty animations resolve immediately
+                               if ( empty ) {
+                                       anim.stop( true );
+                               }
+                       };
 
-                       body.appendChild( iframe );
+               return empty || optall.queue === false ?
+                       this.each( doAnimation ) :
+                       this.queue( optall.queue, doAnimation );
+       },
+       stop: function( type, clearQueue, gotoEnd ) {
+               var stopQueue = function( hooks ) {
+                       var stop = hooks.stop;
+                       delete hooks.stop;
+                       stop( gotoEnd );
+               };
+
+               if ( typeof type !== "string" ) {
+                       gotoEnd = clearQueue;
+                       clearQueue = type;
+                       type = undefined;
+               }
+               if ( clearQueue && type !== false ) {
+                       this.queue( type || "fx", [] );
+               }
+
+               return this.each(function() {
+                       var dequeue = true,
+                               index = type != null && type + "queueHooks",
+                               timers = jQuery.timers,
+                               data = jQuery._data( this );
 
-                       // Create a cacheable copy of the iframe document on first call.
-                       // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
-                       // document to it; WebKit & Firefox won't allow reusing the iframe document.
-                       if ( !iframeDoc || !iframe.createElement ) {
-                               iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
-                               iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
-                               iframeDoc.close();
+                       if ( index ) {
+                               if ( data[ index ] && data[ index ].stop ) {
+                                       stopQueue( data[ index ] );
+                               }
+                       } else {
+                               for ( index in data ) {
+                                       if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+                                               stopQueue( data[ index ] );
+                                       }
+                               }
                        }
 
-                       elem = iframeDoc.createElement( nodeName );
+                       for ( index = timers.length; index--; ) {
+                               if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+                                       timers[ index ].anim.stop( gotoEnd );
+                                       dequeue = false;
+                                       timers.splice( index, 1 );
+                               }
+                       }
 
-                       iframeDoc.body.appendChild( elem );
+                       // start the next in the queue if the last step wasn't forced
+                       // timers currently will call their complete callbacks, which will dequeue
+                       // but only if they were gotoEnd
+                       if ( dequeue || !gotoEnd ) {
+                               jQuery.dequeue( this, type );
+                       }
+               });
+       }
+});
 
-                       display = jQuery.css( elem, "display" );
-                       body.removeChild( iframe );
-               }
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+       var which,
+               attrs = { height: type },
+               i = 0;
+
+       // if we include width, step value is 1 to do all cssExpand values,
+       // if we don't include width, step value is 2 to skip over Left and Right
+       for( ; i < 4 ; i += 2 - includeWidth ) {
+               which = cssExpand[ i ];
+               attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+       }
 
-               // Store the correct default display
-               elemdisplay[ nodeName ] = display;
+       if ( includeWidth ) {
+               attrs.opacity = attrs.width = type;
        }
 
-       return elemdisplay[ nodeName ];
+       return attrs;
 }
 
+// Generate shortcuts for custom animations
+jQuery.each({
+       slideDown: genFx("show"),
+       slideUp: genFx("hide"),
+       slideToggle: genFx("toggle"),
+       fadeIn: { opacity: "show" },
+       fadeOut: { opacity: "hide" },
+       fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+       jQuery.fn[ name ] = function( speed, easing, callback ) {
+               return this.animate( props, speed, easing, callback );
+       };
+});
 
+jQuery.speed = function( speed, easing, fn ) {
+       var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+               complete: fn || !fn && easing ||
+                       jQuery.isFunction( speed ) && speed,
+               duration: speed,
+               easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+       };
 
+       opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+               opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
 
-var getOffset,
-       rtable = /^t(?:able|d|h)$/i,
-       rroot = /^(?:body|html)$/i;
+       // normalize opt.queue - true/undefined/null -> "fx"
+       if ( opt.queue == null || opt.queue === true ) {
+               opt.queue = "fx";
+       }
 
-if ( "getBoundingClientRect" in document.documentElement ) {
-       getOffset = function( elem, doc, docElem, box ) {
-               try {
-                       box = elem.getBoundingClientRect();
-               } catch(e) {}
+       // Queueing
+       opt.old = opt.complete;
 
-               // Make sure we're not dealing with a disconnected DOM node
-               if ( !box || !jQuery.contains( docElem, elem ) ) {
-                       return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+       opt.complete = function() {
+               if ( jQuery.isFunction( opt.old ) ) {
+                       opt.old.call( this );
                }
 
-               var body = doc.body,
-                       win = getWindow( doc ),
-                       clientTop  = docElem.clientTop  || body.clientTop  || 0,
-                       clientLeft = docElem.clientLeft || body.clientLeft || 0,
-                       scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
-                       scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
-                       top  = box.top  + scrollTop  - clientTop,
-                       left = box.left + scrollLeft - clientLeft;
-
-               return { top: top, left: left };
+               if ( opt.queue ) {
+                       jQuery.dequeue( this, opt.queue );
+               }
        };
 
-} else {
-       getOffset = function( elem, doc, docElem ) {
-               var computedStyle,
-                       offsetParent = elem.offsetParent,
-                       prevOffsetParent = elem,
-                       body = doc.body,
-                       defaultView = doc.defaultView,
-                       prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
-                       top = elem.offsetTop,
-                       left = elem.offsetLeft;
-
-               while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
-                       if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
-                               break;
-                       }
+       return opt;
+};
 
-                       computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
-                       top  -= elem.scrollTop;
-                       left -= elem.scrollLeft;
+jQuery.easing = {
+       linear: function( p ) {
+               return p;
+       },
+       swing: function( p ) {
+               return 0.5 - Math.cos( p*Math.PI ) / 2;
+       }
+};
 
-                       if ( elem === offsetParent ) {
-                               top  += elem.offsetTop;
-                               left += elem.offsetLeft;
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+       var timer,
+               timers = jQuery.timers,
+               i = 0;
 
-                               if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
-                                       top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
-                                       left += parseFloat( computedStyle.borderLeftWidth ) || 0;
-                               }
+       for ( ; i < timers.length; i++ ) {
+               timer = timers[ i ];
+               // Checks the timer has not already been removed
+               if ( !timer() && timers[ i ] === timer ) {
+                       timers.splice( i--, 1 );
+               }
+       }
 
-                               prevOffsetParent = offsetParent;
-                               offsetParent = elem.offsetParent;
-                       }
+       if ( !timers.length ) {
+               jQuery.fx.stop();
+       }
+};
 
-                       if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
-                               top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
-                               left += parseFloat( computedStyle.borderLeftWidth ) || 0;
-                       }
+jQuery.fx.timer = function( timer ) {
+       if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
+               timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+       }
+};
 
-                       prevComputedStyle = computedStyle;
-               }
+jQuery.fx.interval = 13;
 
-               if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
-                       top  += body.offsetTop;
-                       left += body.offsetLeft;
-               }
+jQuery.fx.stop = function() {
+       clearInterval( timerId );
+       timerId = null;
+};
 
-               if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
-                       top  += Math.max( docElem.scrollTop, body.scrollTop );
-                       left += Math.max( docElem.scrollLeft, body.scrollLeft );
-               }
+jQuery.fx.speeds = {
+       slow: 600,
+       fast: 200,
+       // Default speed
+       _default: 400
+};
 
-               return { top: top, left: left };
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+       jQuery.expr.filters.animated = function( elem ) {
+               return jQuery.grep(jQuery.timers, function( fn ) {
+                       return elem === fn.elem;
+               }).length;
        };
 }
+var rroot = /^(?:body|html)$/i;
 
 jQuery.fn.offset = function( options ) {
        if ( arguments.length ) {
@@ -9150,18 +8995,35 @@ jQuery.fn.offset = function( options ) {
                        });
        }
 
-       var elem = this[0],
+       var box, docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, top, left,
+               elem = this[ 0 ],
                doc = elem && elem.ownerDocument;
 
        if ( !doc ) {
-               return null;
+               return;
        }
 
-       if ( elem === doc.body ) {
+       if ( (body = doc.body) === elem ) {
                return jQuery.offset.bodyOffset( elem );
        }
 
-       return getOffset( elem, doc, doc.documentElement );
+       docElem = doc.documentElement;
+
+       // Make sure we're not dealing with a disconnected DOM node
+       if ( !jQuery.contains( docElem, elem ) ) {
+               return { top: 0, left: 0 };
+       }
+
+       box = elem.getBoundingClientRect();
+       win = getWindow( doc );
+       clientTop  = docElem.clientTop  || body.clientTop  || 0;
+       clientLeft = docElem.clientLeft || body.clientLeft || 0;
+       scrollTop  = win.pageYOffset || docElem.scrollTop;
+       scrollLeft = win.pageXOffset || docElem.scrollLeft;
+       top  = box.top  + scrollTop  - clientTop;
+       left = box.left + scrollLeft - clientLeft;
+
+       return { top: top, left: left };
 };
 
 jQuery.offset = {
@@ -9227,7 +9089,7 @@ jQuery.fn.extend({
 
        position: function() {
                if ( !this[0] ) {
-                       return null;
+                       return;
                }
 
                var elem = this[0],
@@ -9262,7 +9124,7 @@ jQuery.fn.extend({
                        while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
                                offsetParent = offsetParent.offsetParent;
                        }
-                       return offsetParent;
+                       return offsetParent || document.body;
                });
        }
 });
@@ -9278,8 +9140,7 @@ jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( me
 
                        if ( val === undefined ) {
                                return win ? (prop in win) ? win[ prop ] :
-                                       jQuery.support.boxModel && win.document.documentElement[ method ] ||
-                                               win.document.body[ method ] :
+                                       win.document.documentElement[ method ] :
                                        elem[ method ];
                        }
 
@@ -9303,83 +9164,47 @@ function getWindow( elem ) {
                        elem.defaultView || elem.parentWindow :
                        false;
 }
-
-
-
-
-// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
 jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
-       var clientProp = "client" + name,
-               scrollProp = "scroll" + name,
-               offsetProp = "offset" + name;
-
-       // innerHeight and innerWidth
-       jQuery.fn[ "inner" + name ] = function() {
-               var elem = this[0];
-               return elem ?
-                       elem.style ?
-                       parseFloat( jQuery.css( elem, type, "padding" ) ) :
-                       this[ type ]() :
-                       null;
-       };
-
-       // outerHeight and outerWidth
-       jQuery.fn[ "outer" + name ] = function( margin ) {
-               var elem = this[0];
-               return elem ?
-                       elem.style ?
-                       parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
-                       this[ type ]() :
-                       null;
-       };
-
-       jQuery.fn[ type ] = function( value ) {
-               return jQuery.access( this, function( elem, type, value ) {
-                       var doc, docElemProp, orig, ret;
-
-                       if ( jQuery.isWindow( elem ) ) {
-                               // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
-                               doc = elem.document;
-                               docElemProp = doc.documentElement[ clientProp ];
-                               return jQuery.support.boxModel && docElemProp ||
-                                       doc.body && doc.body[ clientProp ] || docElemProp;
-                       }
-
-                       // Get document width or height
-                       if ( elem.nodeType === 9 ) {
-                               // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
-                               doc = elem.documentElement;
-
-                               // when a window > document, IE6 reports a offset[Width/Height] > client[Width/Height]
-                               // so we can't use max, as it'll choose the incorrect offset[Width/Height]
-                               // instead we use the correct client[Width/Height]
-                               // support:IE6
-                               if ( doc[ clientProp ] >= doc[ scrollProp ] ) {
-                                       return doc[ clientProp ];
+       jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+               // margin is only for outerHeight, outerWidth
+               jQuery.fn[ funcName ] = function( margin, value ) {
+                       var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+                               extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+                       return jQuery.access( this, function( elem, type, value ) {
+                               var doc;
+
+                               if ( jQuery.isWindow( elem ) ) {
+                                       // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+                                       // isn't a whole lot we can do. See pull request at this URL for discussion:
+                                       // https://github.com/jquery/jquery/pull/764
+                                       return elem.document.documentElement[ "client" + name ];
+                               }
+
+                               // Get document width or height
+                               if ( elem.nodeType === 9 ) {
+                                       doc = elem.documentElement;
+
+                                       // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+                                       // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+                                       return Math.max(
+                                               elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+                                               elem.body[ "offset" + name ], doc[ "offset" + name ],
+                                               doc[ "client" + name ]
+                                       );
                                }
 
-                               return Math.max(
-                                       elem.body[ scrollProp ], doc[ scrollProp ],
-                                       elem.body[ offsetProp ], doc[ offsetProp ]
-                               );
-                       }
-
-                       // Get width or height on the element
-                       if ( value === undefined ) {
-                               orig = jQuery.css( elem, type );
-                               ret = parseFloat( orig );
-                               return jQuery.isNumeric( ret ) ? ret : orig;
-                       }
+                               return value === undefined ?
+                                       // Get width or height on the element, requesting but not forcing parseFloat
+                                       jQuery.css( elem, type, value, extra ) :
 
-                       // Set the width or height on the element
-                       jQuery( elem ).css( type, value );
-               }, type, value, arguments.length, null );
-       };
+                                       // Set width or height on the element
+                                       jQuery.style( elem, type, value, extra );
+                       }, type, chainable ? margin : undefined, chainable );
+               };
+       });
 });
-
-
-
-
 // Expose jQuery to the global object
 window.jQuery = window.$ = jQuery;
 
@@ -9399,6 +9224,4 @@ if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
        define( "jquery", [], function () { return jQuery; } );
 }
 
-
-
 })( window );
index 77f2639..33f8752 100644 (file)
@@ -19,7 +19,7 @@
 $.fn.makeCollapsible = function () {
 
        return this.each(function () {
-               var _fn = 'jquery.makeCollapsible> ';
+               var lpx = 'jquery.makeCollapsible> ';
 
                // Define reused variables and functions
                var $toggle,
@@ -248,7 +248,7 @@ $.fn.makeCollapsible = function () {
 
                        var thatId = $that.attr( 'id' ),
                                $customTogglers = $( '.' + thatId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) );
-                       mw.log( _fn + 'Found custom collapsible: #' + thatId );
+                       mw.log( lpx + 'Found custom collapsible: #' + thatId );
 
                        // Double check that there is actually a customtoggle link
                        if ( $customTogglers.length ) {
@@ -256,7 +256,7 @@ $.fn.makeCollapsible = function () {
                                        toggleLinkCustom( $(this), e, $that );
                                } );
                        } else {
-                               mw.log( _fn + '#' + thatId + ': Missing toggler!' );
+                               mw.log( lpx + '#' + thatId + ': Missing toggler!' );
                        }
 
                        // Initial state
index 9f1f3a4..583c1ed 100644 (file)
@@ -8,16 +8,16 @@
                // On IE, patch the focus() method to restore the windows' scroll position
                // (bug 32241)
                $.fn.extend({
-                       focus : ( function ( _focus ) {
+                       focus: ( function ( jqFocus ) {
                                return function () {
                                        if ( arguments.length === 0 ) {
                                                var $w = $( window );
                                                var state = {top: $w.scrollTop(), left: $w.scrollLeft()};
-                                               var result = _focus.apply( this, arguments );
+                                               var result = jqFocus.apply( this, arguments );
                                                window.scrollTo( state.top, state.left );
                                                return result;
                                        }
-                                       return _focus.apply( this, arguments );
+                                       return jqFocus.apply( this, arguments );
                                };
                        }( $.fn.focus ) )
                });
index bd07cd0..1c51c97 100644 (file)
@@ -24,7 +24,7 @@
                                selectText: selectText
                        };
                }
-               var $image = $('<img>', {
+               var $image = $( '<img>', {
                        width : 23,
                        height: 22,
                        src   : b.imageFile,
@@ -96,7 +96,7 @@
        mw.toolbar = toolbar;
 
        $( document ).ready( function () {
-               var buttons, i, b, iframe;
+               var buttons, i, b, $iframe;
 
                // currentFocus is used to determine where to insert tags
                currentFocused = $( '#wpTextbox1' );
 
                // HACK: make currentFocused work with the usability iframe
                // With proper focus detection support (HTML 5!) this'll be much cleaner
-               iframe = $( '.wikiEditor-ui-text iframe' );
-               if ( iframe.length > 0 ) {
-                       $( iframe.get( 0 ).contentWindow.document )
+               // TODO: Get rid of this WikiEditor code from MediaWiki core!
+               $iframe = $( '.wikiEditor-ui-text iframe' );
+               if ( $iframe.length > 0 ) {
+                       $( $iframe.get( 0 ).contentWindow.document )
                                // for IE
-                               .add( iframe.get( 0 ).contentWindow.document.body )
+                               .add( $iframe.get( 0 ).contentWindow.document.body )
                                .focus( function () {
-                                       currentFocused = iframe;
+                                       currentFocused = $iframe;
                                } );
                }
        });
index 76b0e6c..55f799e 100644 (file)
@@ -30,20 +30,20 @@ jQuery( document ).ready( function ( $ ) {
                                return true;
                        }
 
-                       if ( $oldidRadio.prop( 'checked' ) ) { 
+                       if ( $oldidRadio.prop( 'checked' ) ) {
                                oldLi = true;
                                $li.addClass( 'selected' );
                                $oldidRadio.css( 'visibility', 'visible' );
                                $diffRadio.css( 'visibility', 'hidden' );
 
-                       } else if ( $diffRadio.prop( 'checked' ) ) { 
+                       } else if ( $diffRadio.prop( 'checked' ) ) {
                                diffLi = true;
                                $li.addClass( 'selected' );
                                $oldidRadio.css( 'visibility', 'hidden' );
                                $diffRadio.css( 'visibility', 'visible' );
 
                        // This list item has neither checked
-                       } else { 
+                       } else {
                                // We're below the selected radios
                                if ( diffLi && oldLi ) {
                                        $oldidRadio.css( 'visibility', 'visible' );
index b1d906f..7a9ceee 100644 (file)
@@ -1,12 +1,14 @@
 /**
  * This module enables double-click-to-edit functionality
  */
-jQuery( document ).ready( function( $ ) {
-       var url = $( '#ca-edit a' ).attr( 'href' );
-       if ( url ) {
-               mw.util.$content.dblclick( function( e ) {
-                       e.preventDefault();
-                       window.location = url;
-               } );
-       }
-} );
+( function ( mw, $ ) {
+       $( function () {
+               var url = $( '#ca-edit a' ).attr( 'href' );
+               if ( url ) {
+                       mw.util.$content.dblclick( function ( e ) {
+                               e.preventDefault();
+                               window.location = url;
+                       } );
+               }
+       } );
+}( mediaWiki, jQuery ) );
index b791cab..ce3c674 100644 (file)
@@ -1,39 +1,43 @@
-// Exif metadata display for MediaWiki file uploads
-//
-// Add an expand/collapse link and collapse by default if set to
-// (with JS disabled, user will see all items)
-//
+/**
+ * Exif metadata display for MediaWiki file uploads
+ *
+ * Add an expand/collapse link and collapse by default if set to
+ * (with JS disabled, user will see all items)
+ */
+( function ( mw, $ ) {
+       $( function () {
+               var $row, $col, $link,
+                       showText = mw.msg( 'metadata-expand' ),
+                       hideText = mw.msg( 'metadata-collapse' ),
+                       $table = $( '#mw_metadata' ),
+                       $tbody = $table.find( 'tbody' );
 
-jQuery( document ).ready( function( $ ) {
-       var showText = mw.msg( 'metadata-expand' );
-       var hideText = mw.msg( 'metadata-collapse' );
+               if ( !$tbody.length ) {
+                       return;
+               }
 
-       var $table = $( '#mw_metadata' );
-       var $tbody = $table.find( 'tbody' );
-       if ( !$tbody.length ) {
-               return;
-       }
+               $row = $( '<tr class="mw-metadata-show-hide-extended"></tr>' );
+               $col = $( '<td colspan="2"></td>' );
 
-       var $row = $( '<tr class="mw-metadata-show-hide-extended"></tr>' );
-       var $col = $( '<td colspan="2"></td>' );
+               $link = $( '<a>', {
+                       text: showText,
+                       href: '#'
+               }).click(function () {
+                       if ( $table.hasClass( 'collapsed' ) ) {
+                               $( this ).text( hideText );
+                       } else {
+                               $( this ).text( showText );
+                       }
+                       $table.toggleClass( 'expanded collapsed' );
+                       return false;
+               });
 
-       var $link = $( '<a></a>', {
-               'text': showText,
-               'href': '#'
-       }).click(function() {
-               if ( $table.hasClass( 'collapsed' ) ) {
-                       $( this ).text( hideText );
-               } else {
-                       $( this ).text( showText );
-               }
-               $table.toggleClass( 'expanded collapsed' );
-               return false;
-       });
+               $col.append( $link );
+               $row.append( $col );
+               $tbody.append( $row );
 
-       $col.append( $link );
-       $row.append( $col );
-       $tbody.append( $row );
+               // And collapse!
+               $table.addClass( 'collapsed' );
+       } );
 
-       // And collapse!
-       $table.addClass( 'collapsed' );
-} );
+}( mediaWiki, jQuery ) );
\ No newline at end of file
index b49823c..8d2ffae 100644 (file)
@@ -2,7 +2,7 @@
  * Base language object with methods for storing and getting
  * language data.
  */
-( function( $, mw ) {
+( function ( mw, $ ) {
 
        var language = {
                /**
@@ -58,4 +58,4 @@
 
        mw.language = language;
 
-} )( jQuery, mediaWiki );
+}( mediaWiki, jQuery ) );
index 78a6309..1234637 100644 (file)
@@ -3,7 +3,7 @@
  * Language.php in MediaWiki.
  * This adds methods for transforming message text.
  */
-( function( $, mw ) {
+( function ( mw, $ ) {
 
 var language = {
 
@@ -12,13 +12,13 @@ var language = {
         *
         * @param {object} template Template object
         * @format template
-        *      {
-        *              'title': [title of template],
-        *              'parameters': [template parameters]
-        *      }
+        *  {
+        *      'title': [title of template],
+        *      'parameters': [template parameters]
+        *  }
         * @example {{Template:title|params}}
         */
-       'procPLURAL': function( template ) {
+       procPLURAL: function ( template ) {
                if ( template.title && template.parameters && mw.language.convertPlural ) {
                        // Check if we have forms to replace
                        if ( template.parameters.length === 0 ) {
@@ -35,6 +35,7 @@ var language = {
                }
                return '';
        },
+
        /**
         * Plural form transformations, needed for some languages.
         *
@@ -42,12 +43,13 @@ var language = {
         * @param forms array List of plural forms
         * @return string Correct form for quantifier in this language
         */
-       'convertPlural': function( count, forms ){
+       convertPlural: function ( count, forms ){
                if ( !forms || forms.length === 0 ) {
                        return '';
                }
-               return ( parseInt( count, 10 ) == 1 ) ? forms[0] : forms[1];
+               return ( parseInt( count, 10 ) === 1 ) ? forms[0] : forms[1];
        },
+
        /**
         * Pads an array to a specific length by copying the last one element.
         *
@@ -55,38 +57,41 @@ var language = {
         * @param count integer Number of forms required
         * @return array Padded array of forms
         */
-       'preConvertPlural': function( forms, count ) {
+       preConvertPlural: function ( forms, count ) {
                while ( forms.length < count ) {
                        forms.push( forms[ forms.length-1 ] );
                }
                return forms;
        },
+
        /**
         * Converts a number using digitTransformTable.
         *
         * @param {num} number Value to be converted
         * @param {boolean} integer Convert the return value to an integer
         */
-       'convertNumber': function( num, integer ) {
+       convertNumber: function( num, integer ) {
+               var i, tmp, transformTable;
+
                if ( !mw.language.digitTransformTable ) {
                        return num;
                }
                // Set the target Transform table:
-               var transformTable = mw.language.digitTransformTable;
+               transformTable = mw.language.digitTransformTable;
                // Check if the "restore" to Latin number flag is set:
                if ( integer ) {
-                       if ( parseInt( num, 10 ) == num ) {
+                       if ( parseInt( num, 10 ) === num ) {
                                return num;
                        }
-                       var tmp = [];
-                       for ( var i in transformTable ) {
+                       tmp = [];
+                       for ( i in transformTable ) {
                                tmp[ transformTable[ i ] ] = i;
                        }
                        transformTable = tmp;
                }
-               var numberString =  '' + num;
+               var numberString = '' + num;
                var convertedNumber = '';
-               for ( var i = 0; i < numberString.length; i++ ) {
+               for ( i = 0; i < numberString.length; i++ ) {
                        if ( transformTable[ numberString[i] ] ) {
                                convertedNumber += transformTable[numberString[i]];
                        } else {
@@ -95,6 +100,7 @@ var language = {
                }
                return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
        },
+
        /**
         * Provides an alternative text depending on specified gender.
         * Usage {{gender:[gender|user object]|masculine|feminine|neutral}}.
@@ -107,7 +113,7 @@ var language = {
         *
         * @return string
         */
-       'gender': function( gender, forms ) {
+       gender: function( gender, forms ) {
                if ( !forms || forms.length === 0 ) {
                        return '';
                }
@@ -140,9 +146,9 @@ var language = {
        },
 
        // Digit Transform Table, populated by language classes where applicable
-       'digitTransformTable': mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'digitTransformTable' )
+       digitTransformTable: mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'digitTransformTable' )
 };
 
 $.extend( mw.language, language );
 
-} )( jQuery, mediaWiki );
+}( mediaWiki, jQuery ) );
index a5541ef..6a11d3e 100644 (file)
@@ -1,4 +1,4 @@
-( function ( $ ) {
+( function ( mw, $ ) {
 
        mw.page = {};
 
@@ -15,4 +15,4 @@
        // is defined for them.
        $( mw.util.init );
 
-} )( jQuery );
+}( mediaWiki, jQuery ) );
index d3f8433..14557d4 100644 (file)
@@ -2,16 +2,12 @@
  * Animate watch/unwatch links to use asynchronous API requests to
  * watch pages, rather than navigating to a different URI.
  */
-( function ( $, mw, undefined ) {
+( function ( mw, $ ) {
        /**
         * The name of the page to watch or unwatch.
         */
        var title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) );
 
-       // Expose local methods
-       mw.page.watch = {
-               'updateWatchLink': updateWatchLink
-       };
        /**
         * Update the link text, link href attribute and (if applicable)
         * "loading" class.
                return 'view';
        }
 
+       // Expose local methods
+       mw.page.watch = {
+               'updateWatchLink': updateWatchLink
+       };
+
        $( document ).ready( function () {
                var $links = $( '.mw-watchlink a, a.mw-watchlink, ' +
                        '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' +
                });
        });
 
-}( jQuery, mediaWiki ) );
+}( mediaWiki, jQuery ) );
index 00e14fa..cab0bbd 100644 (file)
@@ -1,12 +1,12 @@
 /*
  * JavaScript for Special:ChangeEmail
  */
-( function( $, mw ) {
+( function ( mw, $ ) {
 
 /**
  * Given an email validity status (true, false, null) update the label CSS class
  */
-var updateMailValidityLabel = function( mail ) {
+function updateMailValidityLabel( mail ) {
        var     isValid = mw.util.validateEmail( mail ),
                $label = $( '#mw-emailaddress-validity' );
 
@@ -22,21 +22,21 @@ var updateMailValidityLabel = function( mail ) {
        } else {
                $label.text( mw.msg( 'email-address-validity-invalid' ) ).addClass( 'invalid' ).removeClass( 'valid' );
        }
-};
+}
 
-$( document ).ready( function() {
+$( document ).ready( function () {
        // Lame tip to let user know if its email is valid. See bug 22449
        // Only bind once for 'blur' so that the user can fill it in without errors
        // After that look at every keypress for direct feedback if it was invalid onblur
-       $( '#wpNewEmail' ).one( 'blur', function() {
+       $( '#wpNewEmail' ).one( 'blur', function () {
                if ( $( '#mw-emailaddress-validity' ).length === 0 ) {
                        $(this).after( '<label for="wpNewEmail" id="mw-emailaddress-validity"></label>' );
                }
                updateMailValidityLabel( $(this).val() );
-               $(this).keyup( function() {
+               $(this).keyup( function () {
                        updateMailValidityLabel( $(this).val() );
                } );
        } );
 } );
 
-} )( jQuery, mediaWiki );
+}( mediaWiki, jQuery ) );
index d413f60..808d5fe 100644 (file)
@@ -1,33 +1,37 @@
-/*
+/**
  * JavaScript for Special:JavaScriptTest
  */
-jQuery( document ).ready( function( $ ) {
+( function ( mw, $ ) {
+       $( function () {
 
-       // Create useskin dropdown menu and reload onchange to the selected skin
-       // (only if a framework was found, not on error pages).
-       $( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function() {
+               // Create useskin dropdown menu and reload onchange to the selected skin
+               // (only if a framework was found, not on error pages).
+               $( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function () {
 
-               var     $html = $( '<p><label for="useskin">'
-                               + mw.message( 'javascripttest-pagetext-skins' ).escaped()
-                               + ' '
-                               + '</label></p>' ),
-                       select = '<select name="useskin" id="useskin">';
+                       var     $html = $( '<p><label for="useskin">'
+                                       + mw.message( 'javascripttest-pagetext-skins' ).escaped()
+                                       + ' '
+                                       + '</label></p>' ),
+                               select = '<select name="useskin" id="useskin">';
 
-               // Build <select> further
-               $.each( mw.config.get( 'wgAvailableSkins' ), function( id ) {
-                       select += '<option value="' + id + '"'
-                               + ( mw.config.get( 'skin' ) === id ? ' selected="selected"' : '' )
-                               + '>' + mw.message( 'skinname-' + id ).escaped() + '</option>';
-               } );
-               select += '</select>';
+                       // Build <select> further
+                       $.each( mw.config.get( 'wgAvailableSkins' ), function ( id ) {
+                               select += '<option value="' + id + '"'
+                                       + ( mw.config.get( 'skin' ) === id ? ' selected="selected"' : '' )
+                                       + '>' + mw.message( 'skinname-' + id ).escaped() + '</option>';
+                       } );
+                       select += '</select>';
 
-               // Bind onchange event handler and append to form
-               $html.append(
-                       $( select ).change( function() {
-                               window.location = QUnit.url( { useskin: $(this).val() } );
-                       } )
-               );
+                       // Bind onchange event handler and append to form
+                       $html.append(
+                               $( select ).change( function () {
+                                       window.location = QUnit.url( { useskin: $(this).val() } );
+                               } )
+                       );
 
-               return $html;
+                       return $html;
+               } );
        } );
-} );
+
+}( mediaWiki, jQuery ) );
+
index 3d520f5..7996d93 100644 (file)
@@ -1,5 +1,5 @@
 /* JavaScript for Special:RecentChanges */
-( function( $ ) {
+( function ( mw, $ ) {
 
        var checkboxes = [ 'nsassociated', 'nsinvert' ];
 
                 * Handler to disable/enable the namespace selector checkboxes when the
                 * special 'all' namespace is selected/unselected respectively.
                 */
-               updateCheckboxes: function() {
+               updateCheckboxes: function () {
                        // The option element for the 'all' namespace has an empty value
-                       var isAllNS = ('' === $select.find('option:selected').val() );
+                       var isAllNS = $select.find('option:selected').val() === '';
 
                        // Iterates over checkboxes and propagate the selected option
-                       $.each( checkboxes, function( i, id ) {
+                       $.each( checkboxes, function ( i, id ) {
                                $( '#' + id ).prop( 'disabled', isAllNS );
                        });
                },
 
-               init: function() {
+               init: function () {
                        // Populate
                        $select = $( '#namespace' );
 
@@ -36,4 +36,4 @@
        // Run when document is ready
        $( rc.init );
 
-})( jQuery );
+}( mediaWiki, jQuery ) );
index f7a4b1f..63e8971 100644 (file)
-/*
+/**
  * JavaScript for Special:Upload
  * Note that additional code still lives in skins/common/upload.js
  */
-
-/**
- * Add a preview to the upload form
- */
-jQuery( function( $ ) {
-       /**
-        * Is the FileAPI available with sufficient functionality?
-        */
-       function hasFileAPI(){
-               return typeof window.FileReader !== 'undefined';
-       }
-
+( function ( mw, $ ) {
        /**
-        * Check if this is a recognizable image type...
-        * Also excludes files over 10M to avoid going insane on memory usage.
-        *
-        * @todo is there a way we can ask the browser what's supported in <img>s?
-        * @todo put SVG back after working around Firefox 7 bug <https://bugzilla.wikimedia.org/show_bug.cgi?id=31643>
-        *
-        * @param {File} file
-        * @return boolean
+        * Add a preview to the upload form
         */
-       function fileIsPreviewable( file ) {
-               var     known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'],
-                       tooHuge = 10 * 1024 * 1024;
-               return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge;
-       }
+       $( function ( $ ) {
+               /**
+                * Is the FileAPI available with sufficient functionality?
+                */
+               function hasFileAPI() {
+                       return typeof window.FileReader !== 'undefined';
+               }
 
-       /**
-        * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload
-        * in browsers supporting HTML5 FileAPI.
-        *
-        * As of this writing, known good:
-        * - Firefox 3.6+
-        * - Chrome 7.something
-        *
-        * @todo check file size limits and warn of likely failures
-        *
-        * @param {File} file
-        */
-       function showPreview( file ) {
-               var     previewSize = 180,
-                       thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' +
-                                               '<div class="thumbinner">' +
-                                                       '<div class="mw-small-spinner" style="width: 180px; height: 180px"></div>' +
-                                                       '<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' +
-                                               '</div>' +
-                                       '</div>' );
-               thumb.find( '.filename' ).text( file.name ).end()
-                       .find( '.fileinfo' ).text( prettySize( file.size ) ).end();
+               /**
+                * Check if this is a recognizable image type...
+                * Also excludes files over 10M to avoid going insane on memory usage.
+                *
+                * @todo is there a way we can ask the browser what's supported in <img>s?
+                * @todo put SVG back after working around Firefox 7 bug <https://bugzilla.wikimedia.org/show_bug.cgi?id=31643>
+                *
+                * @param {File} file
+                * @return boolean
+                */
+               function fileIsPreviewable( file ) {
+                       var     known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'],
+                               tooHuge = 10 * 1024 * 1024;
+                       return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge;
+               }
 
-               var     $canvas = $('<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>'),
-                       ctx = $canvas[0].getContext( '2d' );
-               $( '#mw-htmlform-source' ).parent().prepend( thumb );
+               /**
+                * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload
+                * in browsers supporting HTML5 FileAPI.
+                *
+                * As of this writing, known good:
+                * - Firefox 3.6+
+                * - Chrome 7.something
+                *
+                * @todo check file size limits and warn of likely failures
+                *
+                * @param {File} file
+                */
+               function showPreview( file ) {
+                       var     previewSize = 180,
+                               thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' +
+                                                       '<div class="thumbinner">' +
+                                                               '<div class="mw-small-spinner" style="width: 180px; height: 180px"></div>' +
+                                                               '<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' +
+                                                       '</div>' +
+                                               '</div>' );
+                       thumb.find( '.filename' ).text( file.name ).end()
+                               .find( '.fileinfo' ).text( prettySize( file.size ) ).end();
 
-               var meta;
-               fetchPreview( file, function( dataURL ) {
-                       var     img = new Image(),
-                               rotation = 0;
+                       var     $canvas = $('<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>'),
+                               ctx = $canvas[0].getContext( '2d' );
+                       $( '#mw-htmlform-source' ).parent().prepend( thumb );
 
-                       if ( meta && meta.tiff && meta.tiff.Orientation ) {
-                               rotation = (360 - function () {
-                                       // See includes/media/Bitmap.php
-                                       switch ( meta.tiff.Orientation.value ) {
-                                               case 8:
-                                                       return 90;
-                                               case 3:
-                                                       return 180;
-                                               case 6:
-                                                       return 270;
-                                               default:
-                                                       return 0;
-                                       }
-                               }() ) % 360;
-                       }
+                       var meta;
+                       fetchPreview( file, function( dataURL ) {
+                               var     img = new Image(),
+                                       rotation = 0;
 
-                       img.onload = function() {
-                               var width, height, x, y, dx, dy, logicalWidth, logicalHeight;
-                               // Fit the image within the previewSizexpreviewSize box
-                               if ( img.width > img.height ) {
-                                       width = previewSize;
-                                       height = img.height / img.width * previewSize;
-                               } else {
-                                       height = previewSize;
-                                       width = img.width / img.height * previewSize;
+                               if ( meta && meta.tiff && meta.tiff.Orientation ) {
+                                       rotation = ( 360 - ( function () {
+                                               // See includes/media/Bitmap.php
+                                               switch ( meta.tiff.Orientation.value ) {
+                                                       case 8:
+                                                               return 90;
+                                                       case 3:
+                                                               return 180;
+                                                       case 6:
+                                                               return 270;
+                                                       default:
+                                                               return 0;
+                                               }
+                                       }() ) ) % 360;
                                }
-                               // Determine the offset required to center the image
-                               dx = (180 - width) / 2;
-                               dy = (180 - height) / 2;
-                               switch ( rotation ) {
-                                       // If a rotation is applied, the direction of the axis
-                                       // changes as well. You can derive the values below by
-                                       // drawing on paper an axis system, rotate it and see
-                                       // where the positive axis direction is
-                                       case 0:
-                                               x = dx;
-                                               y = dy;
-                                               logicalWidth = img.width;
-                                               logicalHeight = img.height;
-                                               break;
-                                       case 90:
 
-                                               x = dx;
-                                               y = dy - previewSize;
-                                               logicalWidth = img.height;
-                                               logicalHeight = img.width;
-                                               break;
-                                       case 180:
-                                               x = dx - previewSize;
-                                               y = dy - previewSize;
-                                               logicalWidth = img.width;
-                                               logicalHeight = img.height;
-                                               break;
-                                       case 270:
-                                               x = dx - previewSize;
-                                               y = dy;
-                                               logicalWidth = img.height;
-                                               logicalHeight = img.width;
-                                               break;
-                               }
-
-                               ctx.clearRect( 0, 0, 180, 180 );
-                               ctx.rotate( rotation / 180 * Math.PI );
-                               ctx.drawImage( img, x, y, width, height );
-                               thumb.find('.mw-small-spinner').replaceWith($canvas);
+                               img.onload = function () {
+                                       var width, height, x, y, dx, dy, logicalWidth, logicalHeight;
+                                       // Fit the image within the previewSizexpreviewSize box
+                                       if ( img.width > img.height ) {
+                                               width = previewSize;
+                                               height = img.height / img.width * previewSize;
+                                       } else {
+                                               height = previewSize;
+                                               width = img.width / img.height * previewSize;
+                                       }
+                                       // Determine the offset required to center the image
+                                       dx = (180 - width) / 2;
+                                       dy = (180 - height) / 2;
+                                       switch ( rotation ) {
+                                               // If a rotation is applied, the direction of the axis
+                                               // changes as well. You can derive the values below by
+                                               // drawing on paper an axis system, rotate it and see
+                                               // where the positive axis direction is
+                                               case 0:
+                                                       x = dx;
+                                                       y = dy;
+                                                       logicalWidth = img.width;
+                                                       logicalHeight = img.height;
+                                                       break;
+                                               case 90:
 
-                               // Image size
-                               var info = mw.msg( 'widthheight', logicalWidth, logicalHeight ) +
-                                       ', ' + prettySize( file.size );
-                               $( '#mw-upload-thumbnail .fileinfo' ).text( info );
-                       };
-                       img.src = dataURL;
-               }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) {
-                       try {
-                               meta = mw.libs.jpegmeta( data, file.fileName );
-                               meta._binary_data = null;
-                       } catch ( e ) {
-                               meta = null;
-                       }
-               } : null );
-       }
+                                                       x = dx;
+                                                       y = dy - previewSize;
+                                                       logicalWidth = img.height;
+                                                       logicalHeight = img.width;
+                                                       break;
+                                               case 180:
+                                                       x = dx - previewSize;
+                                                       y = dy - previewSize;
+                                                       logicalWidth = img.width;
+                                                       logicalHeight = img.height;
+                                                       break;
+                                               case 270:
+                                                       x = dx - previewSize;
+                                                       y = dy;
+                                                       logicalWidth = img.height;
+                                                       logicalHeight = img.width;
+                                                       break;
+                                       }
 
-       /**
-        * Start loading a file into memory; when complete, pass it as a
-        * data URL to the callback function. If the callbackBinary is set it will
-        * first be read as binary and afterwards as data URL. Useful if you want
-        * to do preprocessing on the binary data first.
-        *
-        * @param {File} file
-        * @param {function} callback
-        * @param {function} callbackBinary
-        */
-       function fetchPreview( file, callback, callbackBinary ) {
-               var reader = new FileReader();
-               if ( callbackBinary && 'readAsBinaryString' in reader ) {
-                       // To fetch JPEG metadata we need a binary string; start there.
-                       // todo: 
-                       reader.onload = function() {
-                               callbackBinary( reader.result );
+                                       ctx.clearRect( 0, 0, 180, 180 );
+                                       ctx.rotate( rotation / 180 * Math.PI );
+                                       ctx.drawImage( img, x, y, width, height );
+                                       thumb.find('.mw-small-spinner').replaceWith($canvas);
 
-                               // Now run back through the regular code path.
-                               fetchPreview( file, callback );
-                       };
-                       reader.readAsBinaryString( file );
-               } else if ( callbackBinary && 'readAsArrayBuffer' in reader ) {
-                       // readAsArrayBuffer replaces readAsBinaryString
-                       // However, our JPEG metadata library wants a string.
-                       // So, this is going to be an ugly conversion.
-                       reader.onload = function() {
-                               var buffer = new Uint8Array( reader.result ),
-                                       string = '';
-                               for ( var i = 0; i < buffer.byteLength; i++ ) {
-                                       string += String.fromCharCode( buffer[i] );
+                                       // Image size
+                                       var info = mw.msg( 'widthheight', logicalWidth, logicalHeight ) +
+                                               ', ' + prettySize( file.size );
+                                       $( '#mw-upload-thumbnail .fileinfo' ).text( info );
+                               };
+                               img.src = dataURL;
+                       }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) {
+                               try {
+                                       meta = mw.libs.jpegmeta( data, file.fileName );
+                                       meta._binary_data = null;
+                               } catch ( e ) {
+                                       meta = null;
                                }
-                               callbackBinary( string );
-
-                               // Now run back through the regular code path.
-                               fetchPreview( file, callback );
-                       };
-                       reader.readAsArrayBuffer( file );
-               } else if ( 'URL' in window && 'createObjectURL' in window.URL ) {
-                       // Supported in Firefox 4.0 and above <https://developer.mozilla.org/en/DOM/window.URL.createObjectURL>
-                       // WebKit has it in a namespace for now but that's ok. ;)
-                       //
-                       // Lifetime of this URL is until document close, which is fine
-                       // for Special:Upload -- if this code gets used on longer-running
-                       // pages, add a revokeObjectURL() when it's no longer needed.
-                       //
-                       // Prefer this over readAsDataURL for Firefox 7 due to bug reading
-                       // some SVG files from data URIs <https://bugzilla.mozilla.org/show_bug.cgi?id=694165>
-                       callback( window.URL.createObjectURL( file ) );
-               } else {
-                       // This ends up decoding the file to base-64 and back again, which
-                       // feels horribly inefficient.
-                       reader.onload = function() {
-                               callback( reader.result );
-                       };
-                       reader.readAsDataURL( file );
+                       } : null );
                }
-       }
 
-       /**
-        * Format a file size attractively.
-        * @todo match numeric formatting
-        *
-        * @param {number} s
-        * @return string
-        */
-       function prettySize( s ) {
-               var sizes = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes'];
-               while ( s >= 1024 && sizes.length > 1 ) {
-                       s /= 1024;
-                       sizes = sizes.slice( 1 );
-               }
-               return mw.msg( sizes[0], Math.round( s ) );
-       }
+               /**
+                * Start loading a file into memory; when complete, pass it as a
+                * data URL to the callback function. If the callbackBinary is set it will
+                * first be read as binary and afterwards as data URL. Useful if you want
+                * to do preprocessing on the binary data first.
+                *
+                * @param {File} file
+                * @param {function} callback
+                * @param {function} callbackBinary
+                */
+               function fetchPreview( file, callback, callbackBinary ) {
+                       var reader = new FileReader();
+                       if ( callbackBinary && 'readAsBinaryString' in reader ) {
+                               // To fetch JPEG metadata we need a binary string; start there.
+                               // todo:
+                               reader.onload = function() {
+                                       callbackBinary( reader.result );
 
-       /**
-        * Clear the file upload preview area.
-        */
-       function clearPreview() {
-               $( '#mw-upload-thumbnail' ).remove();
-       }
+                                       // Now run back through the regular code path.
+                                       fetchPreview( file, callback );
+                               };
+                               reader.readAsBinaryString( file );
+                       } else if ( callbackBinary && 'readAsArrayBuffer' in reader ) {
+                               // readAsArrayBuffer replaces readAsBinaryString
+                               // However, our JPEG metadata library wants a string.
+                               // So, this is going to be an ugly conversion.
+                               reader.onload = function() {
+                                       var buffer = new Uint8Array( reader.result ),
+                                               string = '';
+                                       for ( var i = 0; i < buffer.byteLength; i++ ) {
+                                               string += String.fromCharCode( buffer[i] );
+                                       }
+                                       callbackBinary( string );
 
-       /**
-        * Check if the file does not exceed the maximum size
-        */
-       function checkMaxUploadSize( file ) {
-               function getMaxUploadSize( type ) {
-                       var sizes = mw.config.get( 'wgMaxUploadSize' );
-                       if ( sizes[type] !== undefined ) {
-                               return sizes[type];
+                                       // Now run back through the regular code path.
+                                       fetchPreview( file, callback );
+                               };
+                               reader.readAsArrayBuffer( file );
+                       } else if ( 'URL' in window && 'createObjectURL' in window.URL ) {
+                               // Supported in Firefox 4.0 and above <https://developer.mozilla.org/en/DOM/window.URL.createObjectURL>
+                               // WebKit has it in a namespace for now but that's ok. ;)
+                               //
+                               // Lifetime of this URL is until document close, which is fine
+                               // for Special:Upload -- if this code gets used on longer-running
+                               // pages, add a revokeObjectURL() when it's no longer needed.
+                               //
+                               // Prefer this over readAsDataURL for Firefox 7 due to bug reading
+                               // some SVG files from data URIs <https://bugzilla.mozilla.org/show_bug.cgi?id=694165>
+                               callback( window.URL.createObjectURL( file ) );
+                       } else {
+                               // This ends up decoding the file to base-64 and back again, which
+                               // feels horribly inefficient.
+                               reader.onload = function() {
+                                       callback( reader.result );
+                               };
+                               reader.readAsDataURL( file );
                        }
-                       return sizes['*'];
                }
-               $( '.mw-upload-source-error' ).remove();
 
-               var maxSize = getMaxUploadSize( 'file' );
-               if ( file.size > maxSize ) {
-                       var error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' +
-                                       mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' );
-                       $( '#wpUploadFile' ).after( error );
-                       return false;
+               /**
+                * Format a file size attractively.
+                * @todo match numeric formatting
+                *
+                * @param {number} s
+                * @return string
+                */
+               function prettySize( s ) {
+                       var sizeMsgs = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes'];
+                       while ( s >= 1024 && sizeMsgs.length > 1 ) {
+                               s /= 1024;
+                               sizeMsgs = sizeMsgs.slice( 1 );
+                       }
+                       return mw.msg( sizeMsgs[0], Math.round( s ) );
                }
-               return true;
-       }
-
 
-       /**
-        * Initialization
-        */
-       if ( hasFileAPI() ) {
-               // Update thumbnail when the file selection control is updated.
-               $( '#wpUploadFile' ).change( function() {
-                       clearPreview();
-                       if ( this.files && this.files.length ) {
-                               // Note: would need to be updated to handle multiple files.
-                               var file = this.files[0];
+               /**
+                * Clear the file upload preview area.
+                */
+               function clearPreview() {
+                       $( '#mw-upload-thumbnail' ).remove();
+               }
 
-                               if ( !checkMaxUploadSize( file ) ) {
-                                       return;
+               /**
+                * Check if the file does not exceed the maximum size
+                */
+               function checkMaxUploadSize( file ) {
+                       function getMaxUploadSize( type ) {
+                               var sizes = mw.config.get( 'wgMaxUploadSize' );
+                               if ( sizes[type] !== undefined ) {
+                                       return sizes[type];
                                }
+                               return sizes['*'];
+                       }
+                       $( '.mw-upload-source-error' ).remove();
 
-                               if ( fileIsPreviewable( file ) ) {
-                                       showPreview( file );
-                               }
+                       var maxSize = getMaxUploadSize( 'file' );
+                       if ( file.size > maxSize ) {
+                               var error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' +
+                                               mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' );
+                               $( '#wpUploadFile' ).after( error );
+                               return false;
                        }
-               } );
-       }
-} );
+                       return true;
+               }
 
-/**
- * Disable all upload source fields except the selected one
- */
-jQuery( function ( $ ) {
-       var rows = $( '.mw-htmlform-field-UploadSourceField' );
-       for ( var i = rows.length; i; i-- ) {
-               var row = rows[i - 1];
-               $( 'input[name="wpSourceType"]', row ).change( function () {
-                       var currentRow = row; // Store current row in our own scope
-                       return function () {
-                               $( '.mw-upload-source-error' ).remove();
-                               if ( this.checked ) {
-                                       // Disable all inputs
-                                       $( 'input[name!="wpSourceType"]', rows ).prop( 'disabled', 'disabled' );
-                                       // Re-enable the current one
-                                       $( 'input', currentRow ).prop( 'disabled', false );
+
+               /**
+                * Initialization
+                */
+               if ( hasFileAPI() ) {
+                       // Update thumbnail when the file selection control is updated.
+                       $( '#wpUploadFile' ).change( function () {
+                               clearPreview();
+                               if ( this.files && this.files.length ) {
+                                       // Note: would need to be updated to handle multiple files.
+                                       var file = this.files[0];
+
+                                       if ( !checkMaxUploadSize( file ) ) {
+                                               return;
+                                       }
+
+                                       if ( fileIsPreviewable( file ) ) {
+                                               showPreview( file );
+                                       }
                                }
-                       };
-               }() );
-       }
-} );
+                       } );
+               }
+       } );
+
+       /**
+        * Disable all upload source fields except the selected one
+        */
+       $( function ( $ ) {
+               var i, row,
+                       rows = $( '.mw-htmlform-field-UploadSourceField' );
+               for ( i = rows.length; i; i-- ) {
+                       row = rows[i - 1];
+                       $( 'input[name="wpSourceType"]', row ).change( ( function () {
+                               var currentRow = row; // Store current row in our own scope
+                               return function () {
+                                       $( '.mw-upload-source-error' ).remove();
+                                       if ( this.checked ) {
+                                               // Disable all inputs
+                                               $( 'input[name!="wpSourceType"]', rows ).prop( 'disabled', true );
+                                               // Re-enable the current one
+                                               $( 'input', currentRow ).prop( 'disabled', false );
+                                       }
+                               };
+                       }() ) );
+               }
+       } );
 
+}( mediaWiki, jQuery ) );
index 8d7996c..6b6e586 100644 (file)
@@ -7,7 +7,7 @@
  *
  * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink
  */
-( function( $ ) {
+( function ( mw, $ ) {
 
        /* Local space */
 
         * @param namespace {Number} (optional) Namespace id. If given, title will be taken as-is.
         * @return {Title} this
         */
-var    Title = function( title, namespace ) {
-               this._ns = 0; // integer namespace id
-               this._name = null; // name in canonical 'database' form
-               this._ext = null; // extension
+       function Title( title, namespace ) {
+               this.ns = 0; // integer namespace id
+               this.name = null; // name in canonical 'database' form
+               this.ext = null; // extension
 
                if ( arguments.length === 2 ) {
                        setNameAndExtension( this, title );
-                       this._ns = fixNsId( namespace );
+                       this.ns = fixNsId( namespace );
                } else if ( arguments.length === 1 ) {
                        setAll( this, title );
                }
                return this;
-       },
+       }
 
+var
        /**
         * Strip some illegal chars: control chars, colon, less than, greater than,
         * brackets, braces, pipe, whitespace and normal spaces. This still leaves some insanity
@@ -41,7 +42,7 @@ var   Title = function( title, namespace ) {
         * @param s {String}
         * @return {String}
         */
-       clean = function( s ) {
+       clean = function ( s ) {
                if ( s !== undefined ) {
                        return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' );
                }
@@ -63,14 +64,14 @@ var Title = function( title, namespace ) {
        /**
         * Sanitize name.
         */
-       fixName = function( s ) {
+       fixName = function ( s ) {
                return clean( $.trim( s ) );
        },
 
        /**
         * Sanitize name.
         */
-       fixExt = function( s ) {
+       fixExt = function ( s ) {
                return clean( s );
        },
 
@@ -79,7 +80,7 @@ var   Title = function( title, namespace ) {
         * @param id {Number} Namespace id.
         * @return {Number|Boolean} The id as-is or boolean false if invalid.
         */
-       fixNsId = function( id ) {
+       fixNsId = function ( id ) {
                // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] )
                var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()];
 
@@ -98,9 +99,13 @@ var  Title = function( title, namespace ) {
         * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored).
         * @return {Number|Boolean} Namespace id or boolean false if unrecognized.
         */
-       getNsIdByName = function( ns ) {
-               // toLowerCase throws exception on null/undefined. Return early.
-               if ( ns == null ) {
+       getNsIdByName = function ( ns ) {
+               // Don't cast non-strings to strings, because null or undefined
+               // should not result in returning the id of a potential namespace
+               // called "Null:" (e.g. on nullwiki.example.org)
+               // Also, toLowerCase throws exception on null/undefined, because
+               // it is a String.prototype method.
+               if ( typeof ns !== 'string' ) {
                        return false;
                }
                ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize
@@ -119,7 +124,7 @@ var Title = function( title, namespace ) {
         * @param raw {String}
         * @return {mw.Title}
         */
-       setAll = function( title, s ) {
+       setAll = function ( title, s ) {
                // In normal browsers the match-array contains null/undefined if there's no match,
                // IE returns an empty string.
                var     matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w{1,5}))?$/ ),
@@ -127,14 +132,14 @@ var       Title = function( title, namespace ) {
 
                // Namespace must be valid, and title must be a non-empty string.
                if ( ns_match && typeof matches[2] === 'string' && matches[2] !== '' ) {
-                       title._ns = ns_match;
-                       title._name = fixName( matches[2] );
+                       title.ns = ns_match;
+                       title.name = fixName( matches[2] );
                        if ( typeof matches[3] === 'string' && matches[3] !== '' ) {
-                               title._ext = fixExt( matches[3] );
+                               title.ext = fixExt( matches[3] );
                        }
                } else {
                        // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace.
-                       title._ns = 0;
+                       title.ns = 0;
                        setNameAndExtension( title, s );
                }
                return title;
@@ -147,16 +152,16 @@ var       Title = function( title, namespace ) {
         * @param raw {String}
         * @return {mw.Title}
         */
-       setNameAndExtension = function( title, raw ) {
+       setNameAndExtension = function ( title, raw ) {
                // In normal browsers the match-array contains null/undefined if there's no match,
                // IE returns an empty string.
                var matches = raw.match( /^(?:)?(.*?)(?:\.(\w{1,5}))?$/ );
 
                // Title must be a non-empty string.
                if ( typeof matches[1] === 'string' && matches[1] !== '' ) {
-                       title._name = fixName( matches[1] );
+                       title.name = fixName( matches[1] );
                        if ( typeof matches[2] === 'string' && matches[2] !== '' ) {
-                               title._ext = fixExt( matches[2] );
+                               title.ext = fixExt( matches[2] );
                        }
                } else {
                        throw new Error( 'mw.Title: Could not parse title "' + raw + '"' );
@@ -172,7 +177,7 @@ var Title = function( title, namespace ) {
         * @param title {mixed} prefixed db-key name (string) or instance of Title
         * @return {mixed} Boolean true/false if the information is available. Otherwise null.
         */
-       Title.exists = function( title ) {
+       Title.exists = function ( title ) {
                var type = $.type( title ), obj = Title.exist.pages, match;
                if ( type === 'string' ) {
                        match = obj[title];
@@ -203,7 +208,7 @@ var Title = function( title, namespace ) {
                 * @param state {Boolean} (optional) State of the given titles. Defaults to true.
                 * @return {Boolean}
                 */
-               set: function( titles, state ) {
+               set: function ( titles, state ) {
                        titles = $.isArray( titles ) ? titles : [titles];
                        state = state === undefined ? true : !!state;
                        var pages = this.pages, i, len = titles.length;
@@ -223,8 +228,8 @@ var Title = function( title, namespace ) {
                 * Get the namespace number.
                 * @return {Number}
                 */
-               getNamespaceId: function(){
-                       return this._ns;
+               getNamespaceId: function (){
+                       return this.ns;
                },
 
                /**
@@ -232,19 +237,19 @@ var       Title = function( title, namespace ) {
                 * In NS_MAIN this is '', otherwise namespace name plus ':'
                 * @return {String}
                 */
-               getNamespacePrefix: function(){
-                       return mw.config.get( 'wgFormattedNamespaces' )[this._ns].replace( / /g, '_' ) + (this._ns === 0 ? '' : ':');
+               getNamespacePrefix: function (){
+                       return mw.config.get( 'wgFormattedNamespaces' )[this.ns].replace( / /g, '_' ) + (this.ns === 0 ? '' : ':');
                },
 
                /**
                 * The name, like "Foo_bar"
                 * @return {String}
                 */
-               getName: function() {
-                       if ( $.inArray( this._ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
-                               return this._name;
+               getName: function () {
+                       if ( $.inArray( this.ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
+                               return this.name;
                        } else {
-                               return $.ucFirst( this._name );
+                               return $.ucFirst( this.name );
                        }
                },
 
@@ -252,7 +257,7 @@ var Title = function( title, namespace ) {
                 * The name, like "Foo bar"
                 * @return {String}
                 */
-               getNameText: function() {
+               getNameText: function () {
                        return text( this.getName() );
                },
 
@@ -260,7 +265,7 @@ var Title = function( title, namespace ) {
                 * Get full name in prefixed DB form, like File:Foo_bar.jpg,
                 * most useful for API calls, anything that must identify the "title".
                 */
-               getPrefixedDb: function() {
+               getPrefixedDb: function () {
                        return this.getNamespacePrefix() + this.getMain();
                },
 
@@ -268,7 +273,7 @@ var Title = function( title, namespace ) {
                 * Get full name in text form, like "File:Foo bar.jpg".
                 * @return {String}
                 */
-               getPrefixedText: function() {
+               getPrefixedText: function () {
                        return text( this.getPrefixedDb() );
                },
 
@@ -276,7 +281,7 @@ var Title = function( title, namespace ) {
                 * The main title (without namespace), like "Foo_bar.jpg"
                 * @return {String}
                 */
-               getMain: function() {
+               getMain: function () {
                        return this.getName() + this.getDotExtension();
                },
 
@@ -284,7 +289,7 @@ var Title = function( title, namespace ) {
                 * The "text" form, like "Foo bar.jpg"
                 * @return {String}
                 */
-               getMainText: function() {
+               getMainText: function () {
                        return text( this.getMain() );
                },
 
@@ -292,23 +297,23 @@ var       Title = function( title, namespace ) {
                 * Get the extension (returns null if there was none)
                 * @return {String|null} extension
                 */
-               getExtension: function() {
-                       return this._ext;
+               getExtension: function () {
+                       return this.ext;
                },
 
                /**
                 * Convenience method: return string like ".jpg", or "" if no extension
                 * @return {String}
                 */
-               getDotExtension: function() {
-                       return this._ext === null ? '' : '.' + this._ext;
+               getDotExtension: function () {
+                       return this.ext === null ? '' : '.' + this.ext;
                },
 
                /**
                 * Return the URL to this title
                 * @return {String}
                 */
-               getUrl: function() {
+               getUrl: function () {
                        return mw.util.wikiGetlink( this.toString() );
                },
 
@@ -316,7 +321,7 @@ var Title = function( title, namespace ) {
                 * Whether this title exists on the wiki.
                 * @return {mixed} Boolean true/false if the information is available. Otherwise null.
                 */
-               exists: function() {
+               exists: function () {
                        return Title.exists( this );
                }
        };
@@ -331,4 +336,4 @@ var Title = function( title, namespace ) {
        // Expose
        mw.Title = Title;
 
-})(jQuery);
+}( mediaWiki, jQuery ) );
index 95e5e80..bd12b21 100644 (file)
                                var args = [];
                                $.each( this.query, function ( key, val ) {
                                        var k = Uri.encode( key ),
-                                               vals = val === null ? [ null ] : $.makeArray( val );
+                                               vals = $.isArray( val ) ? val : [ val ];
                                        $.each( vals, function ( i, v ) {
                                                args.push( k + ( v === null ? '' : '=' + Uri.encode( v ) ) );
                                        } );
 
                defaultUri = new Uri( documentLocation );
 
-               return Uri;     
+               return Uri;
        };
 
        // if we are running in a browser, inject the current document location, for relative URLs
-       if ( document && document.location && document.location.href ) { 
+       if ( document && document.location && document.location.href ) {
                mw.Uri = mw.UriRelative( document.location.href );
        }
 
index e631c76..36628eb 100644 (file)
@@ -5,12 +5,13 @@
  * @since 1.19
  */
 
-( function ( $, mw, undefined ) {
-"use strict";
+( function ( mw, $ ) {
+       'use strict';
 
-       var hovzer = $.getFootHovzer();
+       var debug,
+               hovzer = $.getFootHovzer();
 
-       var debug = mw.Debug = {
+       debug = mw.Debug = {
                /**
                 * Toolbar container element
                 *
                        paneTriggerBitDiv( 'includes', 'PHP includes', this.data.includes.length );
 
                        var gitInfo = '';
-                       if ( this.data.gitRevision != false ) {
+                       if ( this.data.gitRevision !== false ) {
                                gitInfo = '(' + this.data.gitRevision.substring( 0, 7 ) + ')';
-                               if ( this.data.gitViewUrl != false ) {
-                                       gitInfo = $( '<a></a>' ).attr( 'href', this.data.gitViewUrl ).text( gitInfo );
+                               if ( this.data.gitViewUrl !== false ) {
+                                       gitInfo = $( '<a>' ).attr( 'href', this.data.gitViewUrl ).text( gitInfo );
                                }
                        }
 
                                .append( ': ' + this.data.mwVersion + ' ' )
                                .append( gitInfo );
 
-                       if ( this.data.gitBranch != false ) {
+                       if ( this.data.gitBranch !== false ) {
                                bitDiv( 'gitbranch' ).text( 'Git branch: ' + this.data.gitBranch );
                        }
 
 
                        $table = $( '<table id="mw-debug-console">' );
 
-                       $('<colgroup>').css( 'width', /*padding=*/20 + ( 10*/*fontSize*/11 ) ).appendTo( $table );
+                       $('<colgroup>').css( 'width', /*padding=*/20 + ( 10 * /*fontSize*/11 ) ).appendTo( $table );
                        $('<colgroup>').appendTo( $table );
                        $('<colgroup>').css( 'width', 350 ).appendTo( $table );
 
                }
        };
 
-} )( jQuery, mediaWiki );
+}( mediaWiki, jQuery ) );
index 9a4a729..f0f78c2 100644 (file)
  * Minimal example in how to use it:
  *
  *    var feedback = new mw.Feedback();
- *    $( '#myButton' ).click( function() { feedback.launch(); } );
+ *    $( '#myButton' ).click( function () { feedback.launch(); } );
  *
  * You can also launch the feedback form with a prefilled subject and body.
  * See the docs for the launch() method.
  */
-( function( mw, $, undefined ) {
+( function ( mw, $ ) {
        /**
         * Thingy for collecting user feedback on a wiki page
         * @param {Array} options -- optional, all properties optional.
-        *              api: {mw.Api} if omitted, will just create a standard API
-        *              title: {mw.Title} the title of the page where you collect feedback. Defaults to "Feedback".
-        *              dialogTitleMessageKey: {String} message key for the title of the dialog box
-        *              bugsLink: {mw.Uri|String} url where bugs can be posted
-        *              bugsListLink: {mw.Uri|String} url where bugs can be listed
+        *  api: {mw.Api} if omitted, will just create a standard API
+        *  title: {mw.Title} the title of the page where you collect feedback. Defaults to "Feedback".
+        *  dialogTitleMessageKey: {String} message key for the title of the dialog box
+        *  bugsLink: {mw.Uri|String} url where bugs can be posted
+        *  bugsListLink: {mw.Uri|String} url where bugs can be listed
         */
-       mw.Feedback = function( options ) {
+       mw.Feedback = function ( options ) {
                if ( options === undefined ) {
                        options = {};
                }
        };
 
        mw.Feedback.prototype = {
-               setup: function() {
-                       var _this = this;
+               setup: function () {
+                       var fb = this;
 
-                       var $feedbackPageLink = $( '<a></a>' )
-                               .attr( { 'href': _this.title.getUrl(), 'target': '_blank' } )
+                       var $feedbackPageLink = $( '<a>' )
+                               .attr( { 'href': fb.title.getUrl(), 'target': '_blank' } )
                                .css( { 'white-space': 'nowrap' } );
 
-                       var $bugNoteLink = $( '<a></a>' ).attr( { 'href': '#' } ).click( function() { _this.displayBugs(); } );
+                       var $bugNoteLink = $( '<a>' ).attr( { 'href': '#' } ).click( function () {
+                               fb.displayBugs();
+                       } );
 
-                       var $bugsListLink = $( '<a></a>' ).attr( { 'href': _this.bugsListLink, 'target': '_blank' } );
+                       var $bugsListLink = $( '<a>' ).attr( { 'href': fb.bugsListLink, 'target': '_blank' } );
 
+                       // TODO: Use a stylesheet instead of these inline styles
                        this.$dialog =
-                               $( '<div style="position:relative;"></div>' ).append(
+                               $( '<div style="position: relative;"></div>' ).append(
                                        $( '<div class="feedback-mode feedback-form"></div>' ).append(
-                                               $( '<small></small>' ).append(
-                                                       $( '<p></p>' ).msg(
+                                               $( '<small>' ).append(
+                                                       $( '<p>' ).msg(
                                                                'feedback-bugornote',
                                                                $bugNoteLink,
-                                                               _this.title.getNameText(),
+                                                               fb.title.getNameText(),
                                                                $feedbackPageLink.clone()
                                                        )
                                                ),
-                                               $( '<div style="margin-top:1em;"></div>' ).append(
+                                               $( '<div style="margin-top: 1em;"></div>' ).append(
                                                        mw.msg( 'feedback-subject' ),
-                                                       $( '<br/>' ),
-                                                       $( '<input type="text" class="feedback-subject" name="subject" maxlength="60" style="width:99%;"/>' )
+                                                       $( '<br>' ),
+                                                       $( '<input type="text" class="feedback-subject" name="subject" maxlength="60" style="width: 99%;"/>' )
                                                ),
-                                               $( '<div style="margin-top:0.4em;"></div>' ).append(
+                                               $( '<div style="margin-top: 0.4em;"></div>' ).append(
                                                        mw.msg( 'feedback-message' ),
-                                                       $( '<br/>' ),
-                                                       $( '<textarea name="message" class="feedback-message" style="width:99%;" rows="5" cols="60"></textarea>' )
+                                                       $( '<br>' ),
+                                                       $( '<textarea name="message" class="feedback-message" style="width: 99%;" rows="5" cols="60"></textarea>' )
                                                )
                                        ),
                                        $( '<div class="feedback-mode feedback-bugs"></div>' ).append(
                                                $( '<p>' ).msg( 'feedback-bugcheck', $bugsListLink )
                                        ),
-                                       $( '<div class="feedback-mode feedback-submitting" style="text-align:center;margin:3em 0;"></div>' ).append(
+                                       $( '<div class="feedback-mode feedback-submitting" style="text-align: center; margin: 3em 0;"></div>' ).append(
                                                mw.msg( 'feedback-adding' ),
                                                $( '<br/>' ),
                                                $( '<span class="feedback-spinner"></span>' )
                                        ),
-                                       $( '<div class="feedback-mode feedback-thanks" style="text-align:center;margin:1em"></div>' ).msg(
-                                               'feedback-thanks', _this.title.getNameText(), $feedbackPageLink.clone()
+                                       $( '<div class="feedback-mode feedback-thanks" style="text-align: center; margin:1em"></div>' ).msg(
+                                               'feedback-thanks', fb.title.getNameText(), $feedbackPageLink.clone()
                                        ),
-                                       $( '<div class="feedback-mode feedback-error" style="position:relative;"></div>' ).append(
-                                               $( '<div class="feedback-error-msg style="color:#990000;margin-top:0.4em;"></div>' )
+                                       $( '<div class="feedback-mode feedback-error" style="position: relative;"></div>' ).append(
+                                               $( '<div class="feedback-error-msg style="color: #990000; margin-top: 0.4em;"></div>' )
                                        )
                                );
 
                                // undo some damage from dialog css
-                               this.$dialog.find( 'a' ).css( { 'color': '#0645ad' } );
+                               this.$dialog.find( 'a' ).css( {
+                                       color: '#0645ad'
+                               } );
 
                                this.$dialog.dialog({
                                        width: 500,
                                        autoOpen: false,
                                        title: mw.msg( this.dialogTitleMessageKey ),
                                        modal: true,
-                                       buttons: _this.buttons
+                                       buttons: fb.buttons
                                });
 
                        this.subjectInput = this.$dialog.find( 'input.feedback-subject' ).get(0);
 
                },
 
-               display: function( s ) {
+               display: function ( s ) {
                        this.$dialog.dialog( { buttons:{} } ); // hide the buttons
                        this.$dialog.find( '.feedback-mode' ).hide(); // hide everything
                        this.$dialog.find( '.feedback-' + s ).show(); // show the desired div
                },
 
-               displaySubmitting: function() {
+               displaySubmitting: function () {
                        this.display( 'submitting' );
                },
 
-               displayBugs: function() {
-                       var _this = this;
+               displayBugs: function () {
+                       var fb = this;
                        this.display( 'bugs' );
                        var bugsButtons = {};
-                       bugsButtons[ mw.msg( 'feedback-bugnew' ) ] = function() { window.open( _this.bugsLink, '_blank' ); };
-                       bugsButtons[ mw.msg( 'feedback-cancel' ) ] = function() { _this.cancel(); };
-                       this.$dialog.dialog( { buttons: bugsButtons } );
+                       bugsButtons[ mw.msg( 'feedback-bugnew' ) ] = function () {
+                               window.open( fb.bugsLink, '_blank' );
+                       };
+                       bugsButtons[ mw.msg( 'feedback-cancel' ) ] = function () {
+                               fb.cancel();
+                       };
+                       this.$dialog.dialog( {
+                               buttons: bugsButtons
+                       } );
                },
 
-               displayThanks: function() {
-                       var _this = this;
+               displayThanks: function () {
+                       var fb = this;
                        this.display( 'thanks' );
                        var closeButton = {};
-                       closeButton[ mw.msg( 'feedback-close' ) ] = function() { _this.$dialog.dialog( 'close' ); };
-                       this.$dialog.dialog( { buttons: closeButton } );
+                       closeButton[ mw.msg( 'feedback-close' ) ] = function () {
+                               fb.$dialog.dialog( 'close' );
+                       };
+                       this.$dialog.dialog( {
+                               buttons: closeButton
+                       } );
                },
 
                /**
                 * Display the feedback form
                 * @param {Object} optional prefilled contents for the feedback form. Object with properties:
-                *                                              subject: {String}
-                *                                              message: {String}
+                *  subject: {String}
+                *      message: {String}
                 */
-               displayForm: function( contents ) {
-                       var _this = this;
-                       this.subjectInput.value = (contents && contents.subject) ? contents.subject : '';
-                       this.messageInput.value = (contents && contents.message) ? contents.message : '';
+               displayForm: function ( contents ) {
+                       var fb = this;
+                       this.subjectInput.value = ( contents && contents.subject ) ? contents.subject : '';
+                       this.messageInput.value = ( contents && contents.message ) ? contents.message : '';
 
                        this.display( 'form' );
 
                        // Set up buttons for dialog box. We have to do it the hard way since the json keys are localized
                        var formButtons = {};
-                       formButtons[ mw.msg( 'feedback-submit' ) ] = function() { _this.submit(); };
-                       formButtons[ mw.msg( 'feedback-cancel' ) ] = function() { _this.cancel(); };
+                       formButtons[ mw.msg( 'feedback-submit' ) ] = function () {
+                               fb.submit();
+                       };
+                       formButtons[ mw.msg( 'feedback-cancel' ) ] = function () {
+                               fb.cancel();
+                       };
                        this.$dialog.dialog( { buttons: formButtons } ); // put the buttons back
                },
 
-               displayError: function( message ) {
-                       var _this = this;
+               displayError: function ( message ) {
+                       var fb = this;
                        this.display( 'error' );
                        this.$dialog.find( '.feedback-error-msg' ).msg( message );
                        var closeButton = {};
-                       closeButton[ mw.msg( 'feedback-close' ) ] = function() { _this.$dialog.dialog( 'close' ); };
+                       closeButton[ mw.msg( 'feedback-close' ) ] = function () {
+                               fb.$dialog.dialog( 'close' );
+                       };
                        this.$dialog.dialog( { buttons: closeButton } );
                },
 
-               cancel: function() {
+               cancel: function () {
                        this.$dialog.dialog( 'close' );
                },
 
-               submit: function() {
-                       var _this = this;
+               submit: function () {
+                       var fb = this;
 
                        // get the values to submit
                        var subject = this.subjectInput.value;
 
-                       var message = "<small>User agent: " + navigator.userAgent + "</small>\n\n"
+                       var message = '<small>User agent: ' + mw.html.escape( navigator.userAgent ) + '</small>\n\n'
                                 + this.messageInput.value;
-                       if ( message.indexOf( '~~~' ) == -1 ) {
-                               message += " ~~~~";
+                       if ( message.indexOf( '~~~' ) === -1 ) {
+                               message += ' ~~~~';
                        }
 
                        this.displaySubmitting();
 
-                       var ok = function( result ) {
+                       var ok = function ( result ) {
                                if ( result.edit !== undefined ) {
                                        if ( result.edit.result === 'Success' ) {
-                                               _this.displayThanks();
+                                               fb.displayThanks();
                                        } else {
-                                               _this.displayError( 'feedback-error1' ); // unknown API result
+                                               // unknown API result
+                                               fb.displayError( 'feedback-error1' );
                                        }
                                } else {
-                                       _this.displayError( 'feedback-error2' ); // edit failed
+                                       // edit failed
+                                       fb.displayError( 'feedback-error2' );
                                }
                        };
 
-                       var err = function( code, info ) {
-                               _this.displayError( 'feedback-error3' ); // ajax request failed
+                       var err = function ( code, info ) {
+                               // ajax request failed
+                               fb.displayError( 'feedback-error3' );
                        };
 
                        this.api.newSection( this.title, subject, message, ok, err );
                 *                                              subject: {String}
                 *                                              message: {String}
                 */
-               launch: function( contents ) {
+               launch: function ( contents ) {
                        this.displayForm( contents );
                        this.$dialog.dialog( 'open' );
                        this.subjectInput.focus();
 
        };
 
-} )( window.mediaWiki, jQuery );
+}( mediaWiki, jQuery ) );
index 17a02cf..df62105 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * Utility functions for jazzing up HTMLForm elements
  */
-( function( $ ) {
+( function ( $ ) {
 
 /**
  * jQuery plugin to fade or snap to visible state.
@@ -9,7 +9,7 @@
  * @param boolean instantToggle (optional)
  * @return jQuery
  */
-$.fn.goIn = function( instantToggle ) {
+$.fn.goIn = function ( instantToggle ) {
        if ( instantToggle === true ) {
                return $(this).show();
        }
@@ -22,7 +22,7 @@ $.fn.goIn = function( instantToggle ) {
  * @param boolean instantToggle (optional)
  * @return jQuery
  */
-$.fn.goOut = function( instantToggle ) {
+$.fn.goOut = function ( instantToggle ) {
        if ( instantToggle === true ) {
                return $(this).hide();
        }
@@ -35,20 +35,20 @@ $.fn.goOut = function( instantToggle ) {
  * @param callback function taking one paramter, which is Bool true when the event
  *     is called immediately, and the EventArgs object when triggered from an event
  */
-$.fn.liveAndTestAtStart = function( callback ){
+$.fn.liveAndTestAtStart = function ( callback ){
        $(this)
                .live( 'change', callback )
-               .each( function( index, element ){
+               .each( function ( index, element ){
                        callback.call( this, true );
                } );
 };
 
 // Document ready:
-$( function() {
+$( function () {
 
        // Animate the SelectOrOther fields, to only show the text field when
        // 'other' is selected.
-       $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function( instant ) {
+       $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) {
                var $other = $( '#' + $(this).attr( 'id' ) + '-other' );
                $other = $other.add( $other.siblings( 'br' ) );
                if ( $(this).val() === 'other' ) {
@@ -61,4 +61,4 @@ $( function() {
 });
 
 
-})( jQuery );
+}( jQuery ) );
index f690a1d..def1225 100644 (file)
@@ -1,22 +1,27 @@
 /**
- * Experimental advanced wikitext parser-emitter. 
- * See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs
- * 
- * @author neilk@wikimedia.org
- */
-
-( function( mw, $, undefined ) {
-
-       mw.jqueryMsg = {};
+* Experimental advanced wikitext parser-emitter.
+* See: http://www.mediawiki.org/wiki/Extension:UploadWizard/MessageParser for docs
+*
+* @author neilk@wikimedia.org
+*/
+( function ( mw, $ ) {
+       var slice = Array.prototype.slice,
+               parserDefaults = {
+                       magic : {
+                               'SITENAME' : mw.config.get( 'wgSiteName' )
+                       },
+                       messages : mw.messages,
+                       language : mw.language
+               };
 
        /**
         * Given parser options, return a function that parses a key and replacements, returning jQuery object
         * @param {Object} parser options
         * @return {Function} accepting ( String message key, String replacement1, String replacement2 ... ) and returning {jQuery}
         */
-       function getFailableParserFn( options ) { 
-               var parser = new mw.jqueryMsg.parser( options ); 
-               /** 
+       function getFailableParserFn( options ) {
+               var parser = new mw.jqueryMsg.parser( options );
+               /**
                 * Try to parse a key and optional replacements, returning a jQuery object that may be a tree of jQuery nodes.
                 * If there was an error parsing, return the key and the error message (wrapped in jQuery). This should put the error right into
                 * the interface, without causing the page to halt script execution, and it hopefully should be clearer how to fix it.
@@ -24,9 +29,9 @@
                 * @param {Array} first element is the key, replacements may be in array in 2nd element, or remaining elements.
                 * @return {jQuery}
                 */
-               return function( args ) {
+               return function ( args ) {
                        var key = args[0];
-                       var argsArray = $.isArray( args[1] ) ? args[1] : $.makeArray( args ).slice( 1 ); 
+                       var argsArray = $.isArray( args[1] ) ? args[1] : slice.call( args, 1 );
                        try {
                                return parser.parse( key, argsArray );
                        } catch ( e ) {
                };
        }
 
+       mw.jqueryMsg = {};
+
        /**
-        * Class method. 
+        * Class method.
         * Returns a function suitable for use as a global, to construct strings from the message key (and optional replacements).
-        * e.g.  
+        * e.g.
         *       window.gM = mediaWiki.parser.getMessageFunction( options );
         *       $( 'p#headline' ).html( gM( 'hello-user', username ) );
         *
         * @param {Array} parser options
         * @return {Function} function suitable for assigning to window.gM
         */
-       mw.jqueryMsg.getMessageFunction = function( options ) { 
+       mw.jqueryMsg.getMessageFunction = function ( options ) {
                var failableParserFn = getFailableParserFn( options );
-               /** 
+               /**
                 * N.B. replacements are variadic arguments or an array in second parameter. In other words:
-                *    somefunction(a, b, c, d) 
-                * is equivalent to 
+                *    somefunction(a, b, c, d)
+                * is equivalent to
                 *    somefunction(a, [b, c, d])
                 *
                 * @param {String} message key
                 * @param {Array} optional replacements (can also specify variadically)
                 * @return {String} rendered HTML as string
                 */
-               return function( /* key, replacements */ ) {
+               return function ( /* key, replacements */ ) {
                        return failableParserFn( arguments ).html();
                };
        };
 
        /**
-        * Class method. 
-        * Returns a jQuery plugin which parses the message in the message key, doing replacements optionally, and appends the nodes to 
-        * the current selector. Bindings to passed-in jquery elements are preserved. Functions become click handlers for [$1 linktext] links.  
-        * e.g.  
+        * Class method.
+        * Returns a jQuery plugin which parses the message in the message key, doing replacements optionally, and appends the nodes to
+        * the current selector. Bindings to passed-in jquery elements are preserved. Functions become click handlers for [$1 linktext] links.
+        * e.g.
         *        $.fn.msg = mediaWiki.parser.getJqueryPlugin( options );
-        *        var userlink = $( '<a>' ).click( function() { alert( "hello!!") } );
+        *        var userlink = $( '<a>' ).click( function () { alert( "hello!!") } );
         *        $( 'p#headline' ).msg( 'hello-user', userlink );
         *
         * @param {Array} parser options
         * @return {Function} function suitable for assigning to jQuery plugin, such as $.fn.msg
         */
-       mw.jqueryMsg.getPlugin = function( options ) {
+       mw.jqueryMsg.getPlugin = function ( options ) {
                var failableParserFn = getFailableParserFn( options );
-               /** 
+               /**
                 * N.B. replacements are variadic arguments or an array in second parameter. In other words:
-                *    somefunction(a, b, c, d) 
-                * is equivalent to 
+                *    somefunction(a, b, c, d)
+                * is equivalent to
                 *    somefunction(a, [b, c, d])
-                * 
+                *
                 * We append to 'this', which in a jQuery plugin context will be the selected elements.
                 * @param {String} message key
                 * @param {Array} optional replacements (can also specify variadically)
                 * @return {jQuery} this
                 */
-               return function( /* key, replacements */ ) {
+               return function ( /* key, replacements */ ) {
                        var $target = this.empty();
-                       $.each( failableParserFn( arguments ).contents(), function( i, node ) {
+                       $.each( failableParserFn( arguments ).contents(), function ( i, node ) {
                                $target.append( node );
                        } );
                        return $target;
                };
        };
 
-       var parserDefaults = { 
-               'magic' : {
-                       'SITENAME' : mw.config.get( 'wgSiteName' )
-               },
-               'messages' : mw.messages,
-               'language' : mw.language
-       };
-
        /**
         * The parser itself.
         * Describes an object, whose primary duty is to .parse() message keys.
         * @param {Array} options
         */
-       mw.jqueryMsg.parser = function( options ) {
+       mw.jqueryMsg.parser = function ( options ) {
                this.settings = $.extend( {}, parserDefaults, options );
                this.emitter = new mw.jqueryMsg.htmlEmitter( this.settings.language, this.settings.magic );
        };
 
        mw.jqueryMsg.parser.prototype = {
-
                // cache, map of mediaWiki message key to the AST of the message. In most cases, the message is a string so this is identical.
                // (This is why we would like to move this functionality server-side).
                astCache: {},
                 * @param {Array} replacements for $1, $2... $n
                 * @return {jQuery}
                 */
-               parse: function( key, replacements ) {
+               parse: function ( key, replacements ) {
                        return this.emitter.emit( this.getAst( key ), replacements );
                },
-
                /**
                 * Fetch the message string associated with a key, return parsed structure. Memoized.
-                * Note that we pass '[' + key + ']' back for a missing message here. 
+                * Note that we pass '[' + key + ']' back for a missing message here.
                 * @param {String} key
                 * @return {String|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing
                 */
-               getAst: function( key ) {
-                       if ( this.astCache[ key ] === undefined ) { 
+               getAst: function ( key ) {
+                       if ( this.astCache[ key ] === undefined ) {
                                var wikiText = this.settings.messages.get( key );
                                if ( typeof wikiText !== 'string' ) {
                                        wikiText = "\\[" + key + "\\]";
                                }
                                this.astCache[ key ] = this.wikiTextToAst( wikiText );
                        }
-                       return this.astCache[ key ];    
+                       return this.astCache[ key ];
                },
-
                /*
                 * Parses the input wikiText into an abstract syntax tree, essentially an s-expression.
                 *
                 * CAVEAT: This does not parse all wikitext. It could be more efficient, but it's pretty good already.
                 * n.b. We want to move this functionality to the server. Nothing here is required to be on the client.
-                * 
+                *
                 * @param {String} message string wikitext
                 * @throws Error
                 * @return {Mixed} abstract syntax tree
                 */
-               wikiTextToAst: function( input ) {
-                       
-                       // Indicates current position in input as we parse through it.  
-                       // Shared among all parsing functions below. 
-                       var pos = 0;
+               wikiTextToAst: function ( input ) {
 
+                       // Indicates current position in input as we parse through it.
+                       // Shared among all parsing functions below.
+                       var pos = 0;
                        // =========================================================
                        // parsing combinators - could be a library on its own
                        // =========================================================
-
-
-                       // Try parsers until one works, if none work return null 
+                       // Try parsers until one works, if none work return null
                        function choice( ps ) {
-                               return function() {
+                               return function () {
                                        for ( var i = 0; i < ps.length; i++ ) {
                                                var result = ps[i]();
                                                if ( result !== null ) {
                                        return null;
                                };
                        }
-
                        // try several ps in a row, all must succeed or return null
                        // this is the only eager one
                        function sequence( ps ) {
                                var originalPos = pos;
                                var result = [];
-                               for ( var i = 0; i < ps.length; i++ ) { 
+                               for ( var i = 0; i < ps.length; i++ ) {
                                        var res = ps[i]();
                                        if ( res === null ) {
                                                pos = originalPos;
                                                return null;
-                                       } 
+                                       }
                                        result.push( res );
                                }
                                return result;
                        }
-
                        // run the same parser over and over until it fails.
                        // must succeed a minimum of n times or return null
                        function nOrMore( n, p ) {
-                               return function() {
+                               return function () {
                                        var originalPos = pos;
                                        var result = [];
                                        var parsed = p();
                                        if ( result.length < n ) {
                                                pos = originalPos;
                                                return null;
-                                       } 
+                                       }
                                        return result;
                                };
                        }
-
                        // There is a general pattern -- parse a thing, if that worked, apply transform, otherwise return null.
                        // But using this as a combinator seems to cause problems when combined with nOrMore().
                        // May be some scoping issue
                        function transform( p, fn ) {
-                               return function() { 
+                               return function () {
                                        var result = p();
                                        return result === null ? null : fn( result );
                                };
                        }
-
                        // Helpers -- just make ps out of simpler JS builtin types
-
-                       function makeStringParser( s ) { 
+                       function makeStringParser( s ) {
                                var len = s.length;
-                               return function() {
+                               return function () {
                                        var result = null;
                                        if ( input.substr( pos, len ) === s ) {
                                                 result = s;
                                        return result;
                                };
                        }
-
                        function makeRegexParser( regex ) {
-                               return function() { 
+                               return function () {
                                        var matches = input.substr( pos ).match( regex );
-                                       if ( matches === null ) { 
+                                       if ( matches === null ) {
                                                return null;
-                                       } 
+                                       }
                                        pos += matches[0].length;
                                        return matches[0];
                                };
                        }
-                                                
 
-                       /** 
-                        *  =================================================================== 
+                       /**
+                        *  ===================================================================
                         *  General patterns above this line -- wikitext specific parsers below
-                        *  =================================================================== 
+                        *  ===================================================================
                         */
-
                        // Parsing functions follow. All parsing functions work like this:
                        // They don't accept any arguments.
                        // Instead, they just operate non destructively on the string 'input'
                        // As they can consume parts of the string, they advance the shared variable pos,
                        // and return tokens (or whatever else they want to return).
-
                        // some things are defined as closures and other things as ordinary functions
                        // converting everything to a closure makes it a lot harder to debug... errors pop up
                        // but some debuggers can't tell you exactly where they come from. Also the mutually
                        // recursive functions seem not to work in all browsers then. (Tested IE6-7, Opera, Safari, FF)
                        // This may be because, to save code, memoization was removed
-
-
-                       var regularLiteral = makeRegexParser( /^[^{}[\]$\\]/ );
-                       var regularLiteralWithoutBar = makeRegexParser(/^[^{}[\]$\\|]/);
-                       var regularLiteralWithoutSpace = makeRegexParser(/^[^{}[\]$\s]/);
-
+                       var regularLiteral = makeRegexParser( /^[^{}\[\]$\\]/ );
+                       var regularLiteralWithoutBar = makeRegexParser(/^[^{}\[\]$\\|]/);
+                       var regularLiteralWithoutSpace = makeRegexParser(/^[^{}\[\]$\s]/);
                        var backslash = makeStringParser( "\\" );
                        var anyCharacter = makeRegexParser( /^./ );
-
                        function escapedLiteral() {
                                var result = sequence( [
-                                       backslash, 
+                                       backslash,
                                        anyCharacter
                                ] );
                                return result === null ? null : result[1];
                        }
-
                        var escapedOrLiteralWithoutSpace = choice( [
                                escapedLiteral,
                                regularLiteralWithoutSpace
                        ] );
-
                        var escapedOrLiteralWithoutBar = choice( [
                                escapedLiteral,
                                regularLiteralWithoutBar
                        ] );
-
-                       var escapedOrRegularLiteral = choice( [ 
+                       var escapedOrRegularLiteral = choice( [
                                escapedLiteral,
                                regularLiteral
                        ] );
-
                        // Used to define "literals" without spaces, in space-delimited situations
                        function literalWithoutSpace() {
                                 var result = nOrMore( 1, escapedOrLiteralWithoutSpace )();
                                 return result === null ? null : result.join('');
                        }
-
-                       // Used to define "literals" within template parameters. The pipe character is the parameter delimeter, so by default 
+                       // Used to define "literals" within template parameters. The pipe character is the parameter delimeter, so by default
                        // it is not a literal in the parameter
                        function literalWithoutBar() {
                                 var result = nOrMore( 1, escapedOrLiteralWithoutBar )();
                                 return result === null ? null : result.join('');
                        }
-
                        function literal() {
                                 var result = nOrMore( 1, escapedOrRegularLiteral )();
                                 return result === null ? null : result.join('');
                        }
-
-                       var whitespace = makeRegexParser( /^\s+/ ); 
+                       var whitespace = makeRegexParser( /^\s+/ );
                        var dollar = makeStringParser( '$' );
-                       var digits = makeRegexParser( /^\d+/ );   
+                       var digits = makeRegexParser( /^\d+/ );
 
                        function replacement() {
                                var result = sequence( [
                                        dollar,
                                        digits
                                ] );
-                               if ( result === null ) { 
+                               if ( result === null ) {
                                        return null;
                                }
                                return [ 'REPLACE', parseInt( result[1], 10 ) - 1 ];
                        }
-
-
                        var openExtlink = makeStringParser( '[' );
                        var closeExtlink = makeStringParser( ']' );
-
                        // this extlink MUST have inner text, e.g. [foo] not allowed; [foo bar] is allowed
                        function extlink() {
                                var result = null;
                                }
                                return result;
                        }
-
                        // this is the same as the above extlink, except that the url is being passed on as a parameter
                        function extLinkParam() {
                                var result = sequence( [
                                }
                                return [ 'LINKPARAM', parseInt( result[2], 10 ) - 1, result[4] ];
                        }
-
                        var openLink = makeStringParser( '[[' );
                        var closeLink = makeStringParser( ']]' );
-
                        function link() {
                                var result = null;
                                var parsedResult = sequence( [
                                }
                                return result;
                        }
-
-                       var templateName = transform( 
+                       var templateName = transform(
                                // see $wgLegalTitleChars
                                // not allowing : due to the need to catch "PLURAL:$1"
-                               makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+-]+/ ),
-                               function( result ) { return result.toString(); }
+                               makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+\-]+/ ),
+                               function ( result ) { return result.toString(); }
                        );
-
                        function templateParam() {
-                               var result = sequence( [ 
+                               var result = sequence( [
                                        pipe,
                                        nOrMore( 0, paramExpression )
                                ] );
                                // use a "CONCAT" operator if there are multiple nodes, otherwise return the first node, raw.
                                return expr.length > 1 ? [ "CONCAT" ].concat( expr ) : expr[0];
                        }
-
                        var pipe = makeStringParser( '|' );
-
                        function templateWithReplacement() {
                                var result = sequence( [
                                        templateName,
                                ] );
                                return result === null ? null : [ result[0], result[2] ];
                        }
-
                        function templateWithOutReplacement() {
                                var result = sequence( [
                                        templateName,
                                ] );
                                return result === null ? null : [ result[0], result[2] ];
                        }
-
                        var colon = makeStringParser(':');
-
                        var templateContents = choice( [
-                               function() {
+                               function () {
                                        var res = sequence( [
                                                // templates can have placeholders for dynamic replacement eg: {{PLURAL:$1|one car|$1 cars}}
                                                // or no placeholders eg: {{GRAMMAR:genitive|{{SITENAME}}}
                                        ] );
                                        return res === null ? null : res[0].concat( res[1] );
                                },
-                               function() { 
+                               function () {
                                        var res = sequence( [
                                                templateName,
-                                               nOrMore( 0, templateParam ) 
+                                               nOrMore( 0, templateParam )
                                        ] );
                                        if ( res === null ) {
                                                return null;
                                        return [ res[0] ].concat( res[1] );
                                }
                        ] );
-
                        var openTemplate = makeStringParser('{{');
                        var closeTemplate = makeStringParser('}}');
-
                        function template() {
                                var result = sequence( [
                                        openTemplate,
                                ] );
                                return result === null ? null : result[1];
                        }
-
                        var nonWhitespaceExpression = choice( [
                                template,
                                link,
                                replacement,
                                literalWithoutSpace
                        ] );
-
                        var paramExpression = choice( [
                                template,
                                link,
                                replacement,
                                literalWithoutBar
                        ] );
-
-                       var expression = choice( [ 
+                       var expression = choice( [
                                template,
                                link,
                                extLinkParam,
                                extlink,
                                replacement,
-                               literal 
+                               literal
                        ] );
-
                        function start() {
                                var result = nOrMore( 0, expression )();
                                if ( result === null ) {
                                }
                                return [ "CONCAT" ].concat( result );
                        }
-
                        // everything above this point is supposed to be stateless/static, but
                        // I am deferring the work of turning it into prototypes & objects. It's quite fast enough
-
                        // finally let's do some actual work...
-
                        var result = start();
-                       
+
                        /*
-                        * For success, the p must have gotten to the end of the input 
+                        * For success, the p must have gotten to the end of the input
                         * and returned a non-null.
                         * n.b. This is part of language infrastructure, so we do not throw an internationalizable message.
                         */
                        }
                        return result;
                }
-                       
-       };
 
+       };
        /**
         * htmlEmitter - object which primarily exists to emit HTML from parser ASTs
         */
-       mw.jqueryMsg.htmlEmitter = function( language, magic ) {
+       mw.jqueryMsg.htmlEmitter = function ( language, magic ) {
                this.language = language;
-               var _this = this;
-
-               $.each( magic, function( key, val ) { 
-                       _this[ key.toLowerCase() ] = function() { return val; };
+               var jmsg = this;
+               $.each( magic, function ( key, val ) {
+                       jmsg[ key.toLowerCase() ] = function () {
+                               return val;
+                       };
                } );
-
                /**
                 * (We put this method definition here, and not in prototype, to make sure it's not overwritten by any magic.)
                 * Walk entire node structure, applying replacements and template functions when appropriate
                 * @param {Array} replacements for $1, $2, ... $n
                 * @return {Mixed} single-string node or array of nodes suitable for jQuery appending
                 */
-               this.emit = function( node, replacements ) {
+               this.emit = function ( node, replacements ) {
                        var ret = null;
-                       var _this = this;
-                       switch( typeof node ) {
+                       var jmsg = this;
+                       switch ( typeof node ) {
                                case 'string':
                                case 'number':
                                        ret = node;
                                        break;
                                case 'object': // node is an array of nodes
-                                       var subnodes = $.map( node.slice( 1 ), function( n ) { 
-                                               return _this.emit( n, replacements );
+                                       var subnodes = $.map( node.slice( 1 ), function ( n ) {
+                                               return jmsg.emit( n, replacements );
                                        } );
                                        var operation = node[0].toLowerCase();
-                                       if ( typeof _this[operation] === 'function' ) { 
-                                               ret = _this[ operation ]( subnodes, replacements );
+                                       if ( typeof jmsg[operation] === 'function' ) {
+                                               ret = jmsg[ operation ]( subnodes, replacements );
                                        } else {
-                                               throw new Error( 'unknown operation "' + operation + '"' );
+                                               throw new Error( 'Unknown operation "' + operation + '"' );
                                        }
                                        break;
                                case 'undefined':
                                        ret = '';
                                        break;
                                default:
-                                       throw new Error( 'unexpected type in AST: ' + typeof node );
+                                       throw new Error( 'Unexpected type in AST: ' + typeof node );
                        }
                        return ret;
                };
-
        };
-
        // For everything in input that follows double-open-curly braces, there should be an equivalent parser
-       // function. For instance {{PLURAL ... }} will be processed by 'plural'. 
+       // function. For instance {{PLURAL ... }} will be processed by 'plural'.
        // If you have 'magic words' then configure the parser to have them upon creation.
        //
        // An emitter method takes the parent node, the array of subnodes and the array of replacements (the values that $1, $2... should translate to).
        // Note: all such functions must be pure, with the exception of referring to other pure functions via this.language (convertPlural and so on)
        mw.jqueryMsg.htmlEmitter.prototype = {
-
                /**
                 * Parsing has been applied depth-first we can assume that all nodes here are single nodes
                 * Must return a single node to parents -- a jQuery with synthetic span
                 * @param {Array} nodes - mixed, some single nodes, some arrays of nodes
                 * @return {jQuery}
                 */
-               concat: function( nodes ) {
-                       var span = $( '<span>' ).addClass( 'mediaWiki_htmlEmitter' );
-                       $.each( nodes, function( i, node ) { 
+               concat: function ( nodes ) {
+                       var $span = $( '<span>' ).addClass( 'mediaWiki_htmlEmitter' );
+                       $.each( nodes, function ( i, node ) {
                                if ( node instanceof jQuery && node.hasClass( 'mediaWiki_htmlEmitter' ) ) {
-                                       $.each( node.contents(), function( j, childNode ) {
-                                               span.append( childNode );
+                                       $.each( node.contents(), function ( j, childNode ) {
+                                               $span.append( childNode );
                                        } );
                                } else {
                                        // strings, integers, anything else
-                                       span.append( node );
+                                       $span.append( node );
                                }
                        } );
-                       return span;
+                       return $span;
                },
 
                /**
                 * @param {Array} of one element, integer, n >= 0
                 * @return {String} replacement
                 */
-               replace: function( nodes, replacements ) {
+               replace: function ( nodes, replacements ) {
                        var index = parseInt( nodes[0], 10 );
-                       
+
                        if ( index < replacements.length ) {
                                if ( typeof arg === 'string' ) {
                                        // replacement is a string, escape it
                        }
                },
 
-               /** 
+               /**
                 * Transform wiki-link
-                * TODO unimplemented 
+                * TODO unimplemented
                 */
-               wlink: function( nodes ) {
-                       return "unimplemented";
+               wlink: function ( nodes ) {
+                       return 'unimplemented';
                },
 
                /**
                 * If the href is a jQuery object, treat it as "enclosing" the link text.
                 *              ... function, treat it as the click handler
                 *              ... string, treat it as a URI
-                * TODO: throw an error if nodes.length > 2 ? 
+                * TODO: throw an error if nodes.length > 2 ?
                 * @param {Array} of two elements, {jQuery|Function|String} and {String}
                 * @return {jQuery}
                 */
-               link: function( nodes ) {
+               link: function ( nodes ) {
                        var arg = nodes[0];
                        var contents = nodes[1];
-                       var $el; 
+                       var $el;
                        if ( arg instanceof jQuery ) {
                                $el = arg;
                        } else {
                                        $el.attr( 'href', arg.toString() );
                                }
                        }
-                       $el.append( contents ); 
+                       $el.append( contents );
                        return $el;
                },
 
                 * @param {Array} of one element, integer, n >= 0
                 * @return {String} replacement
                 */
-               linkparam: function( nodes, replacements ) {
+               linkparam: function ( nodes, replacements ) {
                        var replacement,
                                index = parseInt( nodes[0], 10 );
                        if ( index < replacements.length) {
                 * Transform parsed structure into pluralization
                 * n.b. The first node may be a non-integer (for instance, a string representing an Arabic number).
                 * So convert it back with the current language's convertNumber.
-                * @param {Array} of nodes, [ {String|Number}, {String}, {String} ... ] 
+                * @param {Array} of nodes, [ {String|Number}, {String}, {String} ... ]
                 * @return {String} selected pluralized form according to current language
                 */
-               plural: function( nodes ) { 
+               plural: function ( nodes ) {
                        var count = parseInt( this.language.convertNumber( nodes[0], true ), 10 );
                        var forms = nodes.slice(1);
                        return forms.length ? this.language.convertPlural( count, forms ) : '';
                /**
                 * Transform parsed structure into gender
                 * Usage {{gender:[gender| mw.user object ] | masculine|feminine|neutral}}.
-                * @param {Array} of nodes, [ {String|mw.User}, {String}, {String} , {String} ] 
+                * @param {Array} of nodes, [ {String|mw.User}, {String}, {String} , {String} ]
                 * @return {String} selected gender form according to current language
                 */
-               gender: function( nodes ) { 
+               gender: function ( nodes ) {
                        var gender;
                        if  ( nodes[0] && nodes[0].options instanceof mw.Map ){
                                gender = nodes[0].options.get( 'gender' );
                 * @param {Array} of nodes [{Grammar case eg: genitive}, {String word}]
                 * @return {String} selected grammatical form according to current language
                 */
-               grammar: function( nodes ) {
+               grammar: function ( nodes ) {
                        var form = nodes[0];
                        var word = nodes[1];
                        return word && form && this.language.convertGrammar( word, form );
                }
        };
-
-       // deprecated! don't rely on gM existing.
-       // the window.gM ought not to be required - or if required, not required here. But moving it to extensions breaks it (?!)
+       // Deprecated! don't rely on gM existing.
+       // The window.gM ought not to be required - or if required, not required here.
+       // But moving it to extensions breaks it (?!)
        // Need to fix plugin so it could do attributes as well, then will be okay to remove this.
-       window.gM = mw.jqueryMsg.getMessageFunction(); 
-
+       window.gM = mw.jqueryMsg.getMessageFunction();
        $.fn.msg = mw.jqueryMsg.getPlugin();
-       
+
        // Replace the default message parser with jqueryMsg
        var oldParser = mw.Message.prototype.parser;
-       mw.Message.prototype.parser = function() {
+       mw.Message.prototype.parser = function () {
                // TODO: should we cache the message function so we don't create a new one every time? Benchmark this maybe?
                // Caching is somewhat problematic, because we do need different message functions for different maps, so
                // we'd have to cache the parser as a member of this.map, which sounds a bit ugly.
-               
                // Do not use mw.jqueryMsg unless required
                if ( this.map.get( this.key ).indexOf( '{{' ) < 0 ) {
                        // Fall back to mw.msg's simple parser
                        return oldParser.apply( this );
                }
-               
                var messageFunction = mw.jqueryMsg.getMessageFunction( { 'messages': this.map } );
                return messageFunction( this.key, this.parameters );
        };
 
-} )( mediaWiki, jQuery );
+}( mediaWiki, jQuery ) );
index 41a5078..e0c65e8 100644 (file)
@@ -1,5 +1,3 @@
-/*jslint browser: true, continue: true, white: true, forin: true*/
-/*global jQuery*/
 /*
  * Core MediaWiki JavaScript Library
  */
@@ -9,7 +7,9 @@ var mw = ( function ( $, undefined ) {
 
        /* Private Members */
 
-       var hasOwn = Object.prototype.hasOwnProperty;
+       var hasOwn = Object.prototype.hasOwnProperty,
+               slice = Array.prototype.slice;
+
        /* Object constructors */
 
        /**
@@ -45,7 +45,7 @@ var mw = ( function ( $, undefined ) {
                        var results, i;
 
                        if ( $.isArray( selection ) ) {
-                               selection = $.makeArray( selection );
+                               selection = slice.call( selection );
                                results = {};
                                for ( i = 0; i < selection.length; i += 1 ) {
                                        results[selection[i]] = this.get( selection[i], fallback );
@@ -130,7 +130,7 @@ var mw = ( function ( $, undefined ) {
                this.format = 'plain';
                this.map = map;
                this.key = key;
-               this.parameters = parameters === undefined ? [] : $.makeArray( parameters );
+               this.parameters = parameters === undefined ? [] : slice.call( parameters );
                return this;
        }
 
@@ -141,7 +141,7 @@ var mw = ( function ( $, undefined ) {
                 *
                 * This function will not be called for nonexistent messages.
                 */
-               parser: function() {
+               parser: function () {
                        var parameters = this.parameters;
                        return this.map.get( this.key ).replace( /\$(\d+)/g, function ( str, match ) {
                                var index = parseInt( match, 10 ) - 1;
@@ -168,7 +168,7 @@ var mw = ( function ( $, undefined ) {
                 *
                 * @return string Message as a string in the current form or <key> if key does not exist.
                 */
-               toString: function() {
+               toString: function () {
                        var text;
 
                        if ( !this.exists() ) {
@@ -205,7 +205,7 @@ var mw = ( function ( $, undefined ) {
                 *
                 * @return {string} String form of parsed message
                 */
-               parse: function() {
+               parse: function () {
                        this.format = 'parse';
                        return this.toString();
                },
@@ -215,7 +215,7 @@ var mw = ( function ( $, undefined ) {
                 *
                 * @return {string} String form of plain message
                 */
-               plain: function() {
+               plain: function () {
                        this.format = 'plain';
                        return this.toString();
                },
@@ -225,7 +225,7 @@ var mw = ( function ( $, undefined ) {
                 *
                 * @return {string} String form of html escaped message
                 */
-               escaped: function() {
+               escaped: function () {
                        this.format = 'escaped';
                        return this.toString();
                },
@@ -235,7 +235,7 @@ var mw = ( function ( $, undefined ) {
                 *
                 * @return {string} String form of parsed message
                 */
-               exists: function() {
+               exists: function () {
                        return this.map.exists( this.key );
                }
        };
@@ -247,7 +247,7 @@ var mw = ( function ( $, undefined ) {
                 * Dummy function which in debug mode can be replaced with a function that
                 * emulates console.log in console-less environments.
                 */
-               log: function() { },
+               log: function () { },
 
                /**
                 * @var constructor Make the Map constructor publicly available.
@@ -298,7 +298,7 @@ var mw = ( function ( $, undefined ) {
                        var parameters;
                        // Support variadic arguments
                        if ( parameter_1 !== undefined ) {
-                               parameters = $.makeArray( arguments );
+                               parameters = slice.call( arguments );
                                parameters.shift();
                        } else {
                                parameters = [];
@@ -341,7 +341,7 @@ var mw = ( function ( $, undefined ) {
                         *      {
                         *              'moduleName': {
                         *                      'version': ############## (unix timestamp),
-                        *                      'dependencies': ['required.foo', 'bar.also', ...], (or) function() {}
+                        *                      'dependencies': ['required.foo', 'bar.also', ...], (or) function () {}
                         *                      'group': 'somegroup', (or) null,
                         *                      'source': 'local', 'someforeignwiki', (or) null
                         *                      'state': 'registered', 'loading', 'loaded', 'ready', 'error' or 'missing'
@@ -717,6 +717,7 @@ var mw = ( function ( $, undefined ) {
                         * @param callback Function: Optional callback which will be run when the script is done
                         */
                        function addScript( src, callback, async ) {
+                               /*jshint evil:true */
                                var script, head,
                                        done = false;
 
@@ -731,7 +732,7 @@ var mw = ( function ( $, undefined ) {
                                        script.setAttribute( 'type', 'text/javascript' );
                                        if ( $.isFunction( callback ) ) {
                                                // Attach handlers for all browsers (based on jQuery.ajax)
-                                               script.onload = script.onreadystatechange = function() {
+                                               script.onload = script.onreadystatechange = function () {
 
                                                        if (
                                                                !done
@@ -768,7 +769,7 @@ var mw = ( function ( $, undefined ) {
                                                // scripts only start loading after  the document has been rendered,
                                                // but so be it. Opera users don't deserve faster web pages if their
                                                // browser makes it impossible
-                                               $( function() { document.body.appendChild( script ); } );
+                                               $( function () { document.body.appendChild( script ); } );
                                        } else {
                                                // IE-safe way of getting the <head> . document.documentElement.head doesn't
                                                // work in scripts that run in the <head>
@@ -830,7 +831,7 @@ var mw = ( function ( $, undefined ) {
                                // Execute script
                                try {
                                        script = registry[module].script;
-                                       markModuleReady = function() {
+                                       markModuleReady = function () {
                                                registry[module].state = 'ready';
                                                handlePending( module );
                                        };
@@ -843,7 +844,7 @@ var mw = ( function ( $, undefined ) {
                                                        return;
                                                }
 
-                                               addScript( arr[i], function() {
+                                               addScript( arr[i], function () {
                                                        nestedAddScript( arr, callback, async, i + 1 );
                                                }, async );
                                        };
index ad4c73d..9ce8305 100644 (file)
@@ -6,7 +6,7 @@
  * @author Trevor Parscal <tparscal@wikimedia.org>
  */
 
-( function ( $ ) {
+( function ( mw, $ ) {
 
        /**
         * Logs a message to the console.
@@ -17,7 +17,7 @@
         *
         * @param {String} First in list of variadic messages to output to console.
         */
-       mw.log = function( /* logmsg, logmsg, */ ) {
+       mw.log = function ( /* logmsg, logmsg, */ ) {
                // Turn arguments into an array
                var     args = Array.prototype.slice.call( arguments ),
                        // Allow log messages to use a configured prefix to identify the source window (ie. frame)
@@ -67,4 +67,4 @@
                } );
        };
 
-})( jQuery );
+}( mediaWiki, jQuery ) );
index 48f30df..b5f124b 100644 (file)
@@ -2,7 +2,7 @@
  * Implementation for mediaWiki.user
  */
 
-( function ( $ ) {
+( function ( mw, $ ) {
 
        /**
         * User object
                                id = generateId();
                        }
                        // Set cookie if not set, or renew it if already set
-                       $.cookie( 'mediaWiki.user.id', id, { 'expires': 365, 'path': '/' } );
+                       $.cookie( 'mediaWiki.user.id', id, {
+                               expires: 365,
+                               path: '/'
+                       } );
                        return id;
                };
 
                 *         'expires': 7
                 *     } );
                 */
-               this.bucket = function( key, options ) {
+               this.bucket = function ( key, options ) {
                        options = $.extend( {
                                'buckets': {},
                                'version': 0,
                        // Bucket information is stored as 2 integers, together as version:bucket like: "1:2"
                        if ( typeof cookie === 'string' && cookie.length > 2 && cookie.indexOf( ':' ) > 0 ) {
                                var parts = cookie.split( ':' );
-                               if ( parts.length > 1 && parts[0] == options.version ) {
+                               if ( parts.length > 1 && Number( parts[0] ) === options.version ) {
                                        version = Number( parts[0] );
                                        bucket = String( parts[1] );
                                }
                                        }
                                }
                                if ( options.tracked ) {
-                                       mw.loader.using( 'jquery.clickTracking', function() {
+                                       mw.loader.using( 'jquery.clickTracking', function () {
                                                $.trackAction(
                                                        'mediaWiki.user.bucket:' + key + '@' + version + ':' + bucket
                                                );
        // This is kind of ugly but we're stuck with this for b/c reasons
        mw.user = new User( mw.user.options, mw.user.tokens );
 
-}( jQuery ) );
+}( mediaWiki, jQuery ) );
index 541c77a..d1bfd05 100644 (file)
@@ -1,11 +1,13 @@
 /**
  * Implements mediaWiki.util library
  */
-( function ( $, mw ) {
-       "use strict";
+( function ( mw, $ ) {
+       'use strict';
 
        // Local cache and alias
-       var util = {
+       var hideMessageTimeout,
+               messageBoxEvents = false,
+               util = {
 
                /**
                 * Initialisation
                 * @return {Boolean} True on success, false on failure.
                 */
                jsMessage: function ( message, className ) {
+                       var $messageDiv = $( '#mw-js-message' );
+
                        if ( !arguments.length || message === '' || message === null ) {
-                               $( '#mw-js-message' ).empty().hide();
+                               $messageDiv.empty().hide();
+                               stopHideMessageTimeout();
                                return true; // Emptying and hiding message is intended behaviour, return true
-
                        } else {
                                // We special-case skin structures provided by the software. Skins that
                                // choose to abandon or significantly modify our formatting can just define
                                // an mw-js-message div to start with.
-                               var $messageDiv = $( '#mw-js-message' );
                                if ( !$messageDiv.length ) {
                                        $messageDiv = $( '<div id="mw-js-message"></div>' );
                                        if ( util.$content.parent().length ) {
                                        }
                                }
 
+                               if ( !messageBoxEvents ) {
+                                       messageBoxEvents = true;
+                                       $messageDiv
+                                               .on( {
+                                                       'mouseenter': stopHideMessageTimeout,
+                                                       'mouseleave': startHideMessageTimeout,
+                                                       'click': hideMessage
+                                               } )
+                                               .on( 'click', 'a', function ( e ) {
+                                                       // Prevent links, even those that don't exist yet, from causing the
+                                                       // message box to close when clicked
+                                                       e.stopPropagation();
+                                               } );
+                               }
+
                                if ( className ) {
-                                       $messageDiv.prop( 'class', 'mw-js-message-' + className );
+                                       $messageDiv.prop( 'className', 'mw-js-message-' + className );
                                }
 
                                if ( typeof message === 'object' ) {
                                }
 
                                $messageDiv.slideDown();
+                               startHideMessageTimeout();
                                return true;
                        }
                },
                }
        };
 
+       // Message auto-hide helpers
+       function hideMessage() {
+               $( '#mw-js-message' ).fadeOut( 'slow' );
+       }
+       function stopHideMessageTimeout() {
+               clearTimeout( hideMessageTimeout );
+       }
+       function startHideMessageTimeout() {
+               clearTimeout( hideMessageTimeout );
+               hideMessageTimeout = setTimeout( hideMessage, 5000 );
+       }
+
        mw.util = util;
 
-} )( jQuery, mediaWiki );
+}( mediaWiki, jQuery ) );
index 8c114f0..8c35626 100644 (file)
@@ -116,14 +116,19 @@ span.texhtml {
 #wpTextbox1 {
        clear: both;
 }
+
 #toolbar img {
        cursor: pointer;
 }
+
 div#mw-js-message {
        margin: 1em 5%;
        padding: 0.5em 2.5%;
        border: solid 1px #ddd;
        background-color: #fcfcfc;
+       /* Message hides on-click */
+       /* See also mw.util.jsMessage */
+       cursor: pointer;
 }
 
 /* Edit section links */
index 8f47499..6526e8b 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * MediaWiki legacy wikibits
  */
-(function(){
+( function ( mw ) {
 
 window.clientPC = navigator.userAgent.toLowerCase(); // Get client info
 window.is_gecko = /gecko/.test( clientPC ) &&
@@ -515,55 +515,14 @@ window.redirectToFragment = function( fragment ) {
  * Add a cute little box at the top of the screen to inform the user of
  * something, replacing any preexisting message.
  *
- * @param message String -or- Dom Object  HTML to be put inside the right div
- * @param className String   Used in adding a class; should be different for each
- *   call to allow CSS/JS to hide different boxes.  null = no class used.
- * @return Boolean       True on success, false on failure
+ * @deprecated since 1.17 Use mw.util.jsMessage instead.
+ * @param {String|HTMLElement} message To be put inside the message box.
+ * @param {String} className Used in adding a class; Can be used to selectively
+ *  apply CSS to a certain category of messages.  null = no class used.
+ * @return {Boolean} True on success, false on failure
  */
-window.jsMsg = function( message, className ) {
-       if ( !document.getElementById ) {
-               return false;
-       }
-       // We special-case skin structures provided by the software.  Skins that
-       // choose to abandon or significantly modify our formatting can just define
-       // an mw-js-message div to start with.
-       var messageDiv = document.getElementById( 'mw-js-message' );
-       if ( !messageDiv ) {
-               messageDiv = document.createElement( 'div' );
-               if ( document.getElementById( 'column-content' )
-               && document.getElementById( 'content' ) ) {
-                       // MonoBook, presumably
-                       document.getElementById( 'content' ).insertBefore(
-                               messageDiv,
-                               document.getElementById( 'content' ).firstChild
-                       );
-               } else if ( document.getElementById( 'content' )
-               && document.getElementById( 'article' ) ) {
-                       // Non-Monobook but still recognizable (old-style)
-                       document.getElementById( 'article').insertBefore(
-                               messageDiv,
-                               document.getElementById( 'article' ).firstChild
-                       );
-               } else {
-                       return false;
-               }
-       }
-
-       messageDiv.setAttribute( 'id', 'mw-js-message' );
-       messageDiv.style.display = 'block';
-       if( className ) {
-               messageDiv.setAttribute( 'class', 'mw-js-message-' + className );
-       }
-
-       if ( typeof message === 'object' ) {
-               while ( messageDiv.hasChildNodes() ) { // Remove old content
-                       messageDiv.removeChild( messageDiv.firstChild );
-               }
-               messageDiv.appendChild( message ); // Append new content
-       } else {
-               messageDiv.innerHTML = message;
-       }
-       return true;
+window.jsMsg = function () {
+       return mw.util.jsMessage.apply( mw.util, arguments );
 };
 
 /**
@@ -663,4 +622,4 @@ if ( ie6_bugs ) {
        importScriptURI( mw.config.get( 'stylepath' ) + '/common/IEFixes.js' );
 }
 
-})();
+}( mediaWiki ) );
index 2fe259e..0566e98 100644 (file)
@@ -913,3 +913,17 @@ div.mw-lag-warn-high {
 .tipsy {
        font-size: 127%;
 }
+
+/* jsMessage */
+
+div#mw-js-message {
+       position: absolute;
+       margin: 0;
+       padding: 0.25em 1em;
+       right: 1em;
+       top: 1em;
+       width: 20em;
+       z-index: 10000;
+       -webkit-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.125);
+       box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.125);
+}
index d45e61c..061e58a 100644 (file)
@@ -197,7 +197,7 @@ div.vectorTabs span {
 }
 div.vectorTabs li.selected a,
 div.vectorTabs li.selected a:visited{
-       color: #333333;
+       color: #333;
        text-decoration: none;
 }
 div.vectorTabs li.new a,
@@ -342,7 +342,7 @@ div.vectorMenu li > a {
 }
 div.vectorMenu li.selected a,
 div.vectorMenu li.selected a:visited {
-       color: #333333;
+       color: #333;
        text-decoration: none;
 }
 /* Search */
@@ -369,7 +369,7 @@ div#simpleSearch {
        margin-top: 0.65em;
        position: relative;
        min-height: 1px; /* Gotta trigger hasLayout for IE7 */
-       border: solid 1px #AAAAAA;
+       border: solid 1px #aaa;
        color: black;
        background-color: white;
        /* @embed */
@@ -394,10 +394,10 @@ div#simpleSearch input:focus {
        outline: none;
 }
 div#simpleSearch input.placeholder {
-       color: #999999;
+       color: #999;
 }
 div#simpleSearch input::-webkit-input-placeholder {
-       color: #999999;
+       color: #999;
 }
 div#simpleSearch input#searchInput {
        position: absolute;
@@ -459,7 +459,7 @@ div#mw-panel div.portal {
 }
 div#mw-panel div.portal h5 {
        font-weight: normal;
-       color: #444444;
+       color: #444;
        padding: 0.25em;
        padding-top: 0;
        padding-left: 1.75em;
@@ -520,7 +520,7 @@ div#footer ul li {
        padding: 0;
        padding-top: 0.5em;
        padding-bottom: 0.5em;
-       color: #333333;
+       color: #333;
        font-size: 0.7em;
 }
 div#footer #footer-icons {
@@ -619,7 +619,7 @@ div#footer #footer-places li {
                background-image: url(images/preferences-fade.png);
                background-position: bottom;
                background-repeat: repeat-x;
-               color: #333333;
+               color: #333;
                text-decoration: none;
        }
 #preferences {
@@ -628,14 +628,14 @@ div#footer #footer-places li {
        margin: 0;
        margin-top: -2px;
        clear: both;
-       border: solid 1px #cccccc;
+       border: solid 1px #ccc;
        background-color: #f9f9f9;
        /* @embed */
        background-image: url(images/preferences-base.png);
 }
 #preferences fieldset {
        border: none;
-       border-top: solid 1px #cccccc;
+       border-top: solid 1px #ccc;
 }
 #preferences fieldset.prefsection {
        border: none;
@@ -643,7 +643,7 @@ div#footer #footer-places li {
        margin: 1em;
 }
 #preferences legend {
-       color: #666666;
+       color: #666;
 }
 #preferences fieldset.prefsection legend.mainLegend {
        display: none;
@@ -655,7 +655,7 @@ div#footer #footer-places li {
 .htmlform-tip {
        font-size: x-small;
        padding: .2em 2em;
-       color: #666666;
+       color: #666;
 }
 #preferences div.mw-prefs-buttons {
        padding: 1em;
@@ -784,8 +784,21 @@ div#content a.external[href *=".pdf?"], div#content a.external[href *=".PDF?"],
        position: relative;
        width: 100%;
 }
-#mw-js-message {
+div#mw-js-message {
+       background-color: #fff;
+       background-color: rgba(255, 255, 255, 0.93);
        font-size: 0.8em;
+       position: absolute;
+       margin: 0;
+       padding: 1em 2em;
+       right: 1em;
+       top: 7em;
+       width: 20em;
+       z-index: 10000;
+       border: solid 1px #a7d7f9;
+       border-radius: 0.75em;
+       -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.125);
+       box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.125);
 }
 div#bodyContent {
        line-height: 1.5em;
index cc76b32..8b719e5 100644 (file)
@@ -1,21 +1,21 @@
-/*
+/**
  * Vector-specific scripts
  */
-jQuery( function( $ ) {
-       $( 'div.vectorMenu' ).each( function() {
-               var self = this;
-               $( 'h5:first a:first', this )
+jQuery( function ( $ ) {
+       $( 'div.vectorMenu' ).each( function () {
+               var $el = $( this );
+               $el.find( 'h5:first a:first' )
                        // For accessibility, show the menu when the hidden link in the menu is clicked (bug 24298)
-                       .click( function( e ) {
-                               $( '.menu:first', self ).toggleClass( 'menuForceShow' );
+                       .click( function ( e ) {
+                               $el.find( '.menu:first' ).toggleClass( 'menuForceShow' );
                                e.preventDefault();
-                       })
+                       } )
                        // When the hidden link has focus, also set a class that will change the arrow icon
-                       .focus( function() {
-                               $( self ).addClass( 'vectorMenuFocus' );
-                       })
-                       .blur( function() {
-                               $( self ).removeClass( 'vectorMenuFocus' );
-                       });
-       });
-});
+                       .focus( function () {
+                               $el.addClass( 'vectorMenuFocus' );
+                       } )
+                       .blur( function () {
+                               $el.removeClass( 'vectorMenuFocus' );
+                       } );
+       } );
+} );
index c96eba0..b9b45ae 100644 (file)
@@ -348,6 +348,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * the values given in the order of the columns in the $fields parameter.
         * Note that the rows are sorted by the columns given in $fields.
         *
+        * @since 1.20
+        *
         * @param $table String|Array the table(s) to query
         * @param $fields String|Array the columns to include in the result (and to sort by)
         * @param $condition String|Array "where" condition(s)
@@ -421,14 +423,20 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
         * Useful to turn a database result row as returned by fetchRow() into
         * a pure indexed array.
         *
-        * @static
+        * @since 1.20
+        *
         * @param $r mixed the array to remove string keys from.
         */
        protected static function stripStringKeys( &$r ) {
-               if ( !is_array( $r ) ) return;
+               if ( !is_array( $r ) ) {
+                       return;
+               }
 
                foreach ( $r as $k => $v ) {
-                       if ( is_string( $k ) ) unset( $r[$k] );
+                       if ( is_string( $k ) ) {
+                               unset( $r[$k] );
+                       }
                }
        }
+
 }