Merge "Revert "Clean up user handling in UploadStash""
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 11 Sep 2016 00:35:32 +0000 (00:35 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 11 Sep 2016 00:35:32 +0000 (00:35 +0000)
411 files changed:
RELEASE-NOTES-1.28
autoload.php
docs/uidesign/mediawiki.action.history.diff.html [deleted file]
docs/uidesign/mediawiki.diff.html [new file with mode: 0644]
includes/Block.php
includes/Category.php
includes/CategoryFinder.php
includes/CategoryViewer.php
includes/DefaultSettings.php
includes/Defines.php
includes/EditPage.php
includes/FauxRequest.php
includes/GlobalFunctions.php
includes/HistoryBlob.php
includes/LinkFilter.php
includes/Linker.php
includes/MediaWiki.php
includes/MergeHistory.php
includes/OutputPage.php
includes/PageProps.php
includes/Pingback.php
includes/Preferences.php
includes/PrefixSearch.php
includes/Revision.php
includes/RevisionList.php
includes/ServiceWiring.php
includes/SiteStats.php
includes/Title.php
includes/WatchedItemQueryService.php
includes/WatchedItemStore.php
includes/WebRequest.php
includes/actions/HistoryAction.php
includes/actions/InfoAction.php
includes/actions/RevertAction.php
includes/actions/RollbackAction.php
includes/api/ApiBase.php
includes/api/ApiEditPage.php
includes/api/ApiExpandTemplates.php
includes/api/ApiMain.php
includes/api/ApiQueryAuthManagerInfo.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiStashEdit.php
includes/api/ApiTag.php
includes/api/ApiUpload.php
includes/api/i18n/de.json
includes/api/i18n/en.json
includes/api/i18n/es.json
includes/api/i18n/fr.json
includes/api/i18n/gl.json
includes/api/i18n/he.json
includes/api/i18n/it.json
includes/api/i18n/lij.json [new file with mode: 0644]
includes/api/i18n/qqq.json
includes/api/i18n/ru.json
includes/api/i18n/uk.json
includes/api/i18n/zh-hans.json
includes/auth/AuthManager.php
includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php
includes/cache/BacklinkCache.php
includes/cache/GenderCache.php
includes/cache/HTMLFileCache.php
includes/cache/LinkBatch.php
includes/cache/LinkCache.php
includes/cache/MessageBlobStore.php
includes/cache/MessageCache.php
includes/cache/ObjectFileCache.php [deleted file]
includes/cache/UserCache.php
includes/cache/localisation/LCStoreDB.php
includes/changes/CategoryMembershipChange.php
includes/changes/RecentChange.php
includes/changetags/ChangeTags.php
includes/content/ContentHandler.php
includes/content/JsonContentHandler.php
includes/dao/DBAccessObjectUtils.php
includes/dao/IDBAccessObject.php
includes/db/ChronologyProtector.php
includes/db/DBConnRef.php
includes/db/Database.php
includes/db/DatabaseMysqlBase.php
includes/db/DatabaseOracle.php
includes/db/DatabasePostgres.php
includes/db/DatabaseSqlite.php
includes/db/DatabaseUtility.php
includes/db/IDatabase.php
includes/db/loadbalancer/LBFactory.php
includes/db/loadbalancer/LBFactoryFake.php
includes/db/loadbalancer/LBFactoryMulti.php
includes/db/loadbalancer/LBFactorySimple.php
includes/db/loadbalancer/LBFactorySingle.php
includes/db/loadbalancer/LoadBalancer.php
includes/deferred/DeferredUpdates.php
includes/deferred/SiteStatsUpdate.php
includes/diff/DifferenceEngine.php
includes/externalstore/ExternalStoreDB.php
includes/filebackend/FileBackend.php
includes/filebackend/FileBackendStore.php
includes/filebackend/lockmanager/LockManager.php
includes/filebackend/lockmanager/MemcLockManager.php
includes/filerepo/FileBackendDBRepoWrapper.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignAPIRepo.php
includes/filerepo/ForeignDBViaLBRepo.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/LocalFile.php
includes/import/WikiImporter.php
includes/installer/i18n/ast.json
includes/installer/i18n/de.json
includes/installer/i18n/fr.json
includes/installer/i18n/lij.json [new file with mode: 0644]
includes/installer/i18n/tcy.json
includes/installer/i18n/vi.json
includes/interwiki/ClassicInterwikiLookup.php
includes/interwiki/InterwikiLookup.php
includes/jobqueue/JobQueue.php
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobQueueGroup.php
includes/jobqueue/JobRunner.php
includes/jobqueue/jobs/CategoryMembershipChangeJob.php
includes/jobqueue/jobs/DeleteLinksJob.php
includes/jobqueue/jobs/NullJob.php
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/MapCacheLRU.php
includes/libs/SamplingStatsdClient.php
includes/libs/WaitConditionLoop.php
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/CachedBagOStuff.php
includes/libs/objectcache/ReplicatedBagOStuff.php
includes/libs/objectcache/WANObjectCache.php
includes/logging/LogEntry.php
includes/logging/LogPager.php
includes/objectcache/ObjectCache.php
includes/objectcache/RedisBagOStuff.php
includes/objectcache/SqlBagOStuff.php
includes/page/Article.php
includes/page/ImagePage.php
includes/page/WikiPage.php
includes/pager/IndexPager.php
includes/parser/LinkHolderArray.php
includes/parser/Parser.php
includes/poolcounter/PoolCounterRedis.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderClientHtml.php
includes/resourceloader/ResourceLoaderModule.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/revisiondelete/RevDelLogList.php
includes/revisiondelete/RevDelRevisionList.php
includes/revisiondelete/RevisionDeleter.php
includes/search/SearchDatabase.php
includes/search/SearchEngineFactory.php
includes/search/SearchHighlighter.php
includes/search/SearchMySQL.php
includes/site/DBSiteStore.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specialpage/ChangesListSpecialPage.php
includes/specialpage/LoginSignupSpecialPage.php
includes/specialpage/QueryPage.php
includes/specials/SpecialActiveusers.php
includes/specials/SpecialAllPages.php
includes/specials/SpecialBlockList.php
includes/specials/SpecialBotPasswords.php
includes/specials/SpecialBrokenRedirects.php
includes/specials/SpecialChangeContentModel.php
includes/specials/SpecialContributions.php
includes/specials/SpecialDeletedContributions.php
includes/specials/SpecialDoubleRedirects.php
includes/specials/SpecialExport.php
includes/specials/SpecialLinkSearch.php
includes/specials/SpecialMediaStatistics.php
includes/specials/SpecialMovepage.php
includes/specials/SpecialPagesWithProp.php
includes/specials/SpecialPrefixindex.php
includes/specials/SpecialProtectedpages.php
includes/specials/SpecialRandomInCategory.php
includes/specials/SpecialRandompage.php
includes/specials/SpecialRandomrootpage.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialRecentchangeslinked.php
includes/specials/SpecialRedirect.php
includes/specials/SpecialTags.php
includes/specials/SpecialUndelete.php
includes/specials/SpecialUpload.php
includes/specials/SpecialUploadStash.php
includes/specials/SpecialVersion.php
includes/specials/SpecialWatchlist.php
includes/specials/SpecialWhatlinkshere.php
includes/specials/SpecialWithoutinterwiki.php
includes/specials/pagers/AllMessagesTablePager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/MergeHistoryPager.php
includes/specials/pagers/NewFilesPager.php
includes/specials/pagers/UsersPager.php
includes/upload/UploadBase.php
includes/upload/UploadStash.php
includes/user/BotPassword.php
includes/user/LocalIdLookup.php
includes/user/PasswordReset.php
includes/user/User.php
includes/user/UserArray.php
includes/user/UserNamePrefixSearch.php
includes/utils/BatchRowUpdate.php
includes/utils/RowUpdateGenerator.php
languages/data/ZhConversion.php
languages/i18n/ang.json
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bn.json
languages/i18n/br.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/ce.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/dty.json
languages/i18n/egl.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/ext.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gu.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/id.json
languages/i18n/ilo.json
languages/i18n/io.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/kiu.json
languages/i18n/kk-cyrl.json
languages/i18n/ko.json
languages/i18n/la.json
languages/i18n/lb.json
languages/i18n/lij.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/mr.json
languages/i18n/my.json
languages/i18n/nan.json
languages/i18n/nb.json
languages/i18n/ne.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/oc.json
languages/i18n/pl.json
languages/i18n/ps.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/qu.json
languages/i18n/ro.json
languages/i18n/ru.json
languages/i18n/sa.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sv.json
languages/i18n/tcy.json
languages/i18n/te.json
languages/i18n/tr.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/vi.json
languages/i18n/vo.json
languages/i18n/wa.json
languages/i18n/yi.json
languages/i18n/yue.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/Maintenance.php
maintenance/archives/patch-user_rights.sql
maintenance/backup.inc
maintenance/benchmarks/benchmarkParse.php
maintenance/checkBadRedirects.php
maintenance/checkImages.php
maintenance/checkUsernames.php
maintenance/cleanupSpam.php
maintenance/cleanupTable.inc
maintenance/cleanupTitles.php
maintenance/clearInterwikiCache.php
maintenance/compareParserCache.php
maintenance/deleteDefaultMessages.php
maintenance/dumpLinks.php
maintenance/dumpTextPass.php
maintenance/dumpUploads.php
maintenance/fetchText.php
maintenance/fixDefaultJsonContentPages.php
maintenance/fixDoubleRedirects.php
maintenance/fixUserRegistration.php
maintenance/generateSitemap.php
maintenance/getReplicaServer.php [new file with mode: 0644]
maintenance/getSlaveServer.php
maintenance/importImages.php
maintenance/initEditCount.php
maintenance/language/zhtable/toHK.manual
maintenance/language/zhtable/toTW.manual
maintenance/language/zhtable/toTrad.manual
maintenance/language/zhtable/tradphrases.manual
maintenance/language/zhtable/tradphrases_exclude.manual
maintenance/populateFilearchiveSha1.php
maintenance/populateRevisionLength.php
maintenance/purgeChangedFiles.php
maintenance/purgeChangedPages.php
maintenance/purgeList.php
maintenance/rebuildFileCache.php
maintenance/rebuildImages.php
maintenance/rebuildall.php
maintenance/refreshFileHeaders.php
maintenance/refreshLinks.php
maintenance/removeInvalidEmails.php
maintenance/removeUnusedAccounts.php
maintenance/resetUserTokens.php
maintenance/rollbackEdits.php
maintenance/runBatchedQuery.php
maintenance/runJobs.php
maintenance/showSiteStats.php
maintenance/sql.php
maintenance/storage/checkStorage.php
maintenance/storage/compressOld.php
maintenance/storage/dumpRev.php
maintenance/storage/fixBug20757.php
maintenance/storage/moveToExternal.php
maintenance/storage/orphanStats.php
maintenance/storage/recompressTracked.php
maintenance/storage/resolveStubs.php
maintenance/storage/storageTypeStats.php
maintenance/storage/testCompression.php
maintenance/storage/trackBlobs.php
maintenance/tidyUpBug37714.php
maintenance/updateArticleCount.php
maintenance/updateCollation.php
maintenance/updateSpecialPages.php
maintenance/userOptions.inc
resources/Resources.php
resources/src/mediawiki.action/mediawiki.action.history.diff.css [deleted file]
resources/src/mediawiki.action/mediawiki.action.history.diff.print.css [deleted file]
resources/src/mediawiki/ForeignApi.js
resources/src/mediawiki/api/rollback.js
resources/src/mediawiki/mediawiki.Upload.BookletLayout.js
resources/src/mediawiki/mediawiki.diff.styles.css [new file with mode: 0644]
resources/src/mediawiki/mediawiki.diff.styles.print.css [new file with mode: 0644]
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.notification.hideForPrint.css [deleted file]
resources/src/mediawiki/mediawiki.notification.print.css [new file with mode: 0644]
resources/src/mediawiki/mediawiki.user.js
resources/src/mediawiki/page/gallery-print.css [deleted file]
resources/src/mediawiki/page/gallery.css
resources/src/mediawiki/page/gallery.print.css [new file with mode: 0644]
resources/src/mediawiki/page/rollback.js
tests/TestsAutoLoader.php
tests/browser/environments.yml
tests/parser/DbTestPreviewer.php [new file with mode: 0644]
tests/parser/DbTestRecorder.php [new file with mode: 0644]
tests/parser/DelayedParserTest.php [new file with mode: 0644]
tests/parser/DjVuSupport.php [new file with mode: 0644]
tests/parser/ITestRecorder.php [new file with mode: 0644]
tests/parser/ParserTest.php [new file with mode: 0644]
tests/parser/ParserTestParserHook.php [new file with mode: 0644]
tests/parser/ParserTestResultNormalizer.php [new file with mode: 0644]
tests/parser/TestFileDataProvider.php [new file with mode: 0644]
tests/parser/TestFileIterator.php [new file with mode: 0644]
tests/parser/TestRecorder.php [new file with mode: 0644]
tests/parser/TidySupport.php [new file with mode: 0644]
tests/parser/fuzzTest.php [new file with mode: 0644]
tests/parser/parserTest.inc [deleted file]
tests/parser/parserTestsParserHook.php [deleted file]
tests/parserTests.php
tests/phpunit/Makefile
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/FauxRequestTest.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/WebRequestTest.php
tests/phpunit/includes/api/ApiEditPageTest.php
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/content/JsonContentHandlerTest.php [new file with mode: 0644]
tests/phpunit/includes/db/DatabaseTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/filerepo/file/FileTest.php
tests/phpunit/includes/libs/SamplingStatsdClientTest.php
tests/phpunit/includes/libs/WaitConditionLoopTest.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
tests/phpunit/includes/linker/LinkRendererTest.php
tests/phpunit/includes/parser/NewParserTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderWikiModuleTest.php
tests/phpunit/includes/utils/FileContentsHasherTest.php
tests/phpunit/includes/utils/MWCryptHashTest.php
tests/phpunit/phpunit.php
tests/testHelpers.inc [deleted file]

index fa7d9ca..bd05309 100644 (file)
@@ -29,6 +29,10 @@ production.
 * When $EditSubmitButtonLabelPublish is true, MediaWiki will label the button
   to store-to-database-and-show-to-others as "Publish page"/"Publish changes";
   if false, the default, they will be "Save page"/"Save changes".
+* The 'editcontentmodel' permission is now granted to all logged-in users ('user').
+  instead of just administrators ('sysop'). Documentation for this feature is
+  available at <https://www.mediawiki.org/wiki/Help:ChangeContentModel>.
+* $wgRevisionCacheExpiry is now set to one week by default instead of being disabled.
 
 === New features in 1.28 ===
 * User::isBot() method for checking if an account is a bot role account.
index 652535c..71f1809 100644 (file)
@@ -509,7 +509,7 @@ $wgAutoloadLocalClasses = [
        'GenericArrayObject' => __DIR__ . '/includes/libs/GenericArrayObject.php',
        'GetConfiguration' => __DIR__ . '/maintenance/getConfiguration.php',
        'GetLagTimes' => __DIR__ . '/maintenance/getLagTimes.php',
-       'GetSlaveServer' => __DIR__ . '/maintenance/getSlaveServer.php',
+       'GetSlaveServer' => __DIR__ . '/maintenance/getReplicaServer.php',
        'GetTextMaint' => __DIR__ . '/maintenance/getText.php',
        'GitInfo' => __DIR__ . '/includes/GitInfo.php',
        'GlobalDependency' => __DIR__ . '/includes/cache/CacheDependency.php',
@@ -981,7 +981,6 @@ $wgAutoloadLocalClasses = [
        'ORAResult' => __DIR__ . '/includes/db/DatabaseOracle.php',
        'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php',
        'ObjectFactory' => __DIR__ . '/includes/libs/ObjectFactory.php',
-       'ObjectFileCache' => __DIR__ . '/includes/cache/ObjectFileCache.php',
        'OldChangesList' => __DIR__ . '/includes/changes/OldChangesList.php',
        'OldLocalFile' => __DIR__ . '/includes/filerepo/file/OldLocalFile.php',
        'OracleInstaller' => __DIR__ . '/includes/installer/OracleInstaller.php',
diff --git a/docs/uidesign/mediawiki.action.history.diff.html b/docs/uidesign/mediawiki.action.history.diff.html
deleted file mode 100644 (file)
index 615558f..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" dir="ltr">
-<head>
-       <meta charset="utf-8">
-       <link rel="stylesheet" href="../../resources/src/mediawiki.action/mediawiki.action.history.diff.css">
-       <link rel="stylesheet" media="print" href="../../resources/src/mediawiki.action/mediawiki.action.history.diff.print.css">
-</head>
-<body>
-
-<p>This show various styles for our diff action. Style sheet: <code><a href="../../resources/src/mediawiki.action/mediawiki.action.history.diff.css">resources/src/mediawiki.action/mediawiki.action.history.diff.css</a></code>.</p>
-<p>This file might help us fix our diff colors which have been a recurring issues among the community for a loooong time.</p>
-<p>Try it out in print mode, too. Style sheet: <code><a href="../../resources/src/mediawiki.action/mediawiki.action.history.diff.print.css">resources/src/mediawiki.action/mediawiki.action.history.diff.print.css</a></code>.</p>
-
-<p>Practical example copied from MediaWiki's HTML output:</p>
-
-<table class="diff diff-contentalign-left">
-       <colgroup><col class="diff-marker">
-       <col class="diff-content">
-       <col class="diff-marker">
-       <col class="diff-content">
-       </colgroup>
-<tbody>
-<tr>
-       <td class="diff-marker">−</td>
-       <td class="diff-deletedline"><div>Lorem ipsum dolor sit amet<del class="diffchange diffchange-inline">, consectetur adipisicing elit</del>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div></td>
-       <td class="diff-marker">+</td>
-       <td class="diff-addedline"><div>Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div></td>
-</tr>
-<tr>
-       <td class="diff-marker">−</td>
-       <td class="diff-deletedline"></td>
-       <td colspan="2" class="diff-empty">&nbsp;</td>
-</tr>
-<tr>
-       <td class="diff-marker">−</td>
-       <td class="diff-deletedline"><div>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div></td>
-       <td colspan="2" class="diff-empty">&nbsp;</td>
-</tr>
-<tr>
-       <td class="diff-marker">&nbsp;</td>
-       <td class="diff-context"></td>
-       <td class="diff-marker">&nbsp;</td>
-       <td class="diff-context"></td>
-</tr>
-<tr>
-       <td class="diff-marker">&nbsp;</td>
-       <td class="diff-context"><div>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</div></td>
-       <td class="diff-marker">&nbsp;</td>
-       <td class="diff-context"><div>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</div></td>
-</tr>
-<tr>
-       <td class="diff-marker">&nbsp;</td>
-       <td class="diff-context"></td>
-       <td class="diff-marker">&nbsp;</td>
-       <td class="diff-context"></td>
-</tr>
-<tr>
-       <td class="diff-marker">−</td>
-       <td class="diff-deletedline"><div>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim<del class="diffchange diffchange-inline"> id est laborum</del>.</div></td>
-       <td class="diff-marker">+</td>
-       <td class="diff-addedline"><div>Excepteur sint occaecat cupidatat non proident, sunt<ins class="diffchange diffchange-inline"> reprehenderit in voluptate</ins> in culpa qui officia deserunt mollit anim.</div></td>
-</tr>
-<tr>
-       <td colspan="2" class="diff-empty">&nbsp;</td>
-       <td class="diff-marker">+</td>
-       <td class="diff-addedline"></td>
-</tr>
-<tr>
-       <td colspan="2" class="diff-empty">&nbsp;</td>
-       <td class="diff-marker">+</td>
-       <td class="diff-addedline"><div>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div></td>
-</tr>
-</tbody></table>
-
-<p>Below are some basic lines being applied one or two classes. Mainly for debugging purposes.</p>
-
-<table class="diff">
-       <tr><th>Diff</th></tr>
-
-       <tr><td class="diff-addedline"><code>diff-addedline</code>: added line</td></tr>
-       <tr><td class="diff-deletedline"><code>diff-deletedline</code>: deleted line</td></tr>
-       <tr><td class="diff-context"><code>diff-context</code>: context</td></tr>
-
-       <tr><th>Same as above with a <code>&lt;ins&gt;</code> or <code>&lt;del&gt;</code> child element having the <code>diffchange</code> class:</th></tr>
-
-       <tr><td class="diffchange">Diffchange</td></tr>
-       <tr><td class="diff-addedline"><ins class="diffchange">Added line + diffchange</ins></td></tr>
-       <tr><td class="diff-deletedline"><del class="diffchange">Deleted line + diffchange</del></td></tr>
-</table>
-
-</body>
-</html>
diff --git a/docs/uidesign/mediawiki.diff.html b/docs/uidesign/mediawiki.diff.html
new file mode 100644 (file)
index 0000000..0372595
--- /dev/null
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+       <meta charset="utf-8">
+       <link rel="stylesheet" href="../../resources/src/mediawiki/mediawiki.diff.styles.css">
+       <link rel="stylesheet" media="print" href="../../resources/src/mediawiki/mediawiki.diff.styles.print.css">
+</head>
+<body>
+
+<p>This show various styles for our diff action. Style sheet: <code><a href="../../resources/src/mediawiki/mediawiki.diff.styles.css">resources/src/mediawiki/mediawiki.diff.styles.css</a></code>.</p>
+<p>This file might help us fix our diff colors which have been a recurring issues among the community for a loooong time.</p>
+<p>Try it out in print mode, too. Style sheet: <code><a href="../../resources/src/mediawiki/mediawiki.diff.styles.print.css">resources/src/mediawiki/mediawiki.diff.styles.print.css</a></code>.</p>
+
+<p>Practical example copied from MediaWiki's HTML output:</p>
+
+<table class="diff diff-contentalign-left">
+       <colgroup><col class="diff-marker">
+       <col class="diff-content">
+       <col class="diff-marker">
+       <col class="diff-content">
+       </colgroup>
+<tbody>
+<tr>
+       <td class="diff-marker">−</td>
+       <td class="diff-deletedline"><div>Lorem ipsum dolor sit amet<del class="diffchange diffchange-inline">, consectetur adipisicing elit</del>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div></td>
+       <td class="diff-marker">+</td>
+       <td class="diff-addedline"><div>Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div></td>
+</tr>
+<tr>
+       <td class="diff-marker">−</td>
+       <td class="diff-deletedline"></td>
+       <td colspan="2" class="diff-empty">&nbsp;</td>
+</tr>
+<tr>
+       <td class="diff-marker">−</td>
+       <td class="diff-deletedline"><div>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div></td>
+       <td colspan="2" class="diff-empty">&nbsp;</td>
+</tr>
+<tr>
+       <td class="diff-marker">&nbsp;</td>
+       <td class="diff-context"></td>
+       <td class="diff-marker">&nbsp;</td>
+       <td class="diff-context"></td>
+</tr>
+<tr>
+       <td class="diff-marker">&nbsp;</td>
+       <td class="diff-context"><div>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</div></td>
+       <td class="diff-marker">&nbsp;</td>
+       <td class="diff-context"><div>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</div></td>
+</tr>
+<tr>
+       <td class="diff-marker">&nbsp;</td>
+       <td class="diff-context"></td>
+       <td class="diff-marker">&nbsp;</td>
+       <td class="diff-context"></td>
+</tr>
+<tr>
+       <td class="diff-marker">−</td>
+       <td class="diff-deletedline"><div>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim<del class="diffchange diffchange-inline"> id est laborum</del>.</div></td>
+       <td class="diff-marker">+</td>
+       <td class="diff-addedline"><div>Excepteur sint occaecat cupidatat non proident, sunt<ins class="diffchange diffchange-inline"> reprehenderit in voluptate</ins> in culpa qui officia deserunt mollit anim.</div></td>
+</tr>
+<tr>
+       <td colspan="2" class="diff-empty">&nbsp;</td>
+       <td class="diff-marker">+</td>
+       <td class="diff-addedline"></td>
+</tr>
+<tr>
+       <td colspan="2" class="diff-empty">&nbsp;</td>
+       <td class="diff-marker">+</td>
+       <td class="diff-addedline"><div>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div></td>
+</tr>
+</tbody></table>
+
+<p>Below are some basic lines being applied one or two classes. Mainly for debugging purposes.</p>
+
+<table class="diff">
+       <tr><th>Diff</th></tr>
+
+       <tr><td class="diff-addedline"><code>diff-addedline</code>: added line</td></tr>
+       <tr><td class="diff-deletedline"><code>diff-deletedline</code>: deleted line</td></tr>
+       <tr><td class="diff-context"><code>diff-context</code>: context</td></tr>
+
+       <tr><th>Same as above with a <code>&lt;ins&gt;</code> or <code>&lt;del&gt;</code> child element having the <code>diffchange</code> class:</th></tr>
+
+       <tr><td class="diffchange">Diffchange</td></tr>
+       <tr><td class="diff-addedline"><ins class="diffchange">Added line + diffchange</ins></td></tr>
+       <tr><td class="diff-deletedline"><del class="diffchange">Deleted line + diffchange</del></td></tr>
+</table>
+
+</body>
+</html>
index 79b31bb..19ba0a2 100644 (file)
@@ -145,7 +145,7 @@ class Block {
 
                $this->mReason = $options['reason'];
                $this->mTimestamp = wfTimestamp( TS_MW, $options['timestamp'] );
-               $this->mExpiry = wfGetDB( DB_SLAVE )->decodeExpiry( $options['expiry'] );
+               $this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] );
 
                # Boolean settings
                $this->mAuto = (bool)$options['auto'];
@@ -168,7 +168,7 @@ class Block {
         * @return Block|null
         */
        public static function newFromID( $id ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->selectRow(
                        'ipblocks',
                        self::selectFields(),
@@ -242,7 +242,7 @@ class Block {
         * @return bool Whether a relevant block was found
         */
        protected function newLoad( $vagueTarget = null ) {
-               $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE );
+               $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_REPLICA );
 
                if ( $this->type !== null ) {
                        $conds = [
@@ -351,7 +351,7 @@ class Block {
                # range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
                # so we can improve performance by filtering on a LIKE clause
                $chunk = self::getIpFragment( $start );
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $like = $dbr->buildLike( $chunk, $dbr->anyString() );
 
                # Fairly hard to make a malicious SQL statement out of hex characters,
@@ -405,7 +405,7 @@ class Block {
                $this->mParentBlockId = $row->ipb_parent_block_id;
 
                // I wish I didn't have to do this
-               $this->mExpiry = wfGetDB( DB_SLAVE )->decodeExpiry( $row->ipb_expiry );
+               $this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $row->ipb_expiry );
 
                $this->isHardblock( !$row->ipb_anon_only );
                $this->isAutoblocking( $row->ipb_enable_autoblock );
@@ -569,7 +569,7 @@ class Block {
         */
        protected function getDatabaseArray( $db = null ) {
                if ( !$db ) {
-                       $db = wfGetDB( DB_SLAVE );
+                       $db = wfGetDB( DB_REPLICA );
                }
                $expiry = $db->encodeExpiry( $this->mExpiry );
 
@@ -653,7 +653,7 @@ class Block {
                        return;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $options = [ 'ORDER BY' => 'rc_timestamp DESC' ];
                $conds = [ 'rc_user_text' => (string)$block->getTarget() ];
@@ -1110,7 +1110,7 @@ class Block {
         * @param array $ipChain List of IPs (strings), usually retrieved from the
         *         X-Forwarded-For header of the request
         * @param bool $isAnon Exclude anonymous-only blocks if false
-        * @param bool $fromMaster Whether to query the master or slave database
+        * @param bool $fromMaster Whether to query the master or replica DB
         * @return array Array of Blocks
         * @since 1.22
         */
@@ -1146,7 +1146,7 @@ class Block {
                if ( $fromMaster ) {
                        $db = wfGetDB( DB_MASTER );
                } else {
-                       $db = wfGetDB( DB_SLAVE );
+                       $db = wfGetDB( DB_REPLICA );
                }
                $conds = $db->makeList( $conds, LIST_OR );
                if ( !$isAnon ) {
index 531e0be..1ebd605 100644 (file)
@@ -60,7 +60,7 @@ class Category {
                        return true;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $row = $dbr->selectRow(
                        'category',
                        [ 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ],
@@ -264,7 +264,7 @@ class Category {
         */
        public function getMembers( $limit = false, $offset = '' ) {
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $conds = [ 'cl_to' => $this->getName(), 'cl_from = page_id' ];
                $options = [ 'ORDER BY' => 'cl_sortkey' ];
index 3d5e6c5..4efa2ff 100644 (file)
@@ -64,7 +64,7 @@ class CategoryFinder {
        /** @var string "AND" or "OR" */
        protected $mode;
 
-       /** @var IDatabase Read-DB slave */
+       /** @var IDatabase Read-DB replica DB */
        protected $dbr;
 
        /**
@@ -96,7 +96,7 @@ class CategoryFinder {
         * @return array Array of page_ids (those given to seed() that match the conditions)
         */
        public function run() {
-               $this->dbr = wfGetDB( DB_SLAVE );
+               $this->dbr = wfGetDB( DB_REPLICA );
                while ( count( $this->next ) > 0 ) {
                        $this->scanNextLayer();
                }
index 490f548..a8e988f 100644 (file)
@@ -286,7 +286,7 @@ class CategoryViewer extends ContextSource {
        }
 
        function doCategoryQuery() {
-               $dbr = wfGetDB( DB_SLAVE, 'category' );
+               $dbr = wfGetDB( DB_REPLICA, 'category' );
 
                $this->nextPage = [
                        'page' => null,
index 2b55f1b..c7fda14 100644 (file)
@@ -1895,7 +1895,7 @@ $wgSharedSchema = false;
  *   - password:    DB password
  *   - type:        DB type
  *
- *   - load:        Ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0.
+ *   - load:        Ratio of DB_REPLICA load, must be >=0, the sum of all loads must be >0.
  *                  If this is zero for any given server, no normal query traffic will be
  *                  sent to it. It will be excluded from lag checks in maintenance scripts.
  *                  The only way it can receive traffic is if groupLoads is used.
@@ -1913,7 +1913,7 @@ $wgSharedSchema = false;
  *                  - DBO_COMPRESS -- uses internal compression in database connections,
  *                                    if available
  *
- *   - max lag:     (optional) Maximum replication lag before a slave will taken out of rotation
+ *   - max lag:     (optional) Maximum replication lag before a replica DB goes out of rotation
  *   - is static:   (optional) Set to true if the dataset is static and no replication is used.
  *   - cliMode:     (optional) Connection handles will not assume that requests are short-lived
  *                  nor that INSERT..SELECT can be rewritten into a buffered SELECT and INSERT.
@@ -1927,15 +1927,15 @@ $wgSharedSchema = false;
  * perhaps in some command-line scripts).
  *
  * The first server listed in this array (with key 0) will be the master. The
- * rest of the servers will be slaves. To prevent writes to your slaves due to
+ * rest of the servers will be replica DBs. To prevent writes to your replica DBs due to
  * accidental misconfiguration or MediaWiki bugs, set read_only=1 on all your
- * slaves in my.cnf. You can set read_only mode at runtime using:
+ * replica DBs in my.cnf. You can set read_only mode at runtime using:
  *
  * @code
  *     SET @@read_only=1;
  * @endcode
  *
- * Since the effect of writing to a slave is so damaging and difficult to clean
+ * Since the effect of writing to a replica DB is so damaging and difficult to clean
  * up, we at Wikimedia set read_only=1 in my.cnf on all our DB servers, even
  * our masters, and then set read_only=0 on masters at runtime.
  */
@@ -2123,7 +2123,7 @@ $wgDefaultExternalStore = false;
  *
  * Set to 0 to disable, or number of seconds before cache expiry.
  */
-$wgRevisionCacheExpiry = 0;
+$wgRevisionCacheExpiry = 86400 * 7;
 
 /** @} */ # end text storage }
 
@@ -2281,7 +2281,8 @@ $wgObjectCaches = [
                        'class' => 'SqlBagOStuff',
                        'args'  => [ [ 'slaveOnly' => false ] ]
                ],
-               'loggroup'  => 'SQLBagOStuff'
+               'loggroup'  => 'SQLBagOStuff',
+               'reportDupes' => false
        ],
 
        'apc' => [ 'class' => 'APCBagOStuff', 'reportDupes' => false ],
@@ -2660,7 +2661,7 @@ $wgInternalServer = false;
 $wgSquidMaxage = 18000;
 
 /**
- * Cache timeout for the CDN when DB slave lag is high
+ * Cache timeout for the CDN when DB replica DB lag is high
  * @see $wgSquidMaxage
  * @since 1.27
  */
@@ -2670,7 +2671,7 @@ $wgCdnMaxageLagged = 30;
  * If set, any SquidPurge call on a URL or URLs will send a second purge no less than
  * this many seconds later via the job queue. This requires delayed job support.
  * This should be safely higher than the 'max lag' value in $wgLBFactoryConf, so that
- * slave lag does not cause page to be stuck in stales states in CDN.
+ * replica DB lag does not cause page to be stuck in stales states in CDN.
  *
  * This also fixes race conditions in two-tiered CDN setups (e.g. cdn2 => cdn1 => MediaWiki).
  * If a purge for a URL reaches cdn2 before cdn1 and a request reaches cdn2 for that URL,
@@ -5076,6 +5077,7 @@ $wgGroupPermissions['user']['purge'] = true;
 $wgGroupPermissions['user']['sendemail'] = true;
 $wgGroupPermissions['user']['applychangetags'] = true;
 $wgGroupPermissions['user']['changetags'] = true;
+$wgGroupPermissions['user']['editcontentmodel'] = true;
 
 // Implicit group for accounts that pass $wgAutoConfirmAge
 $wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true;
@@ -5106,7 +5108,6 @@ $wgGroupPermissions['sysop']['undelete'] = true;
 $wgGroupPermissions['sysop']['editinterface'] = true;
 $wgGroupPermissions['sysop']['editusercss'] = true;
 $wgGroupPermissions['sysop']['edituserjs'] = true;
-$wgGroupPermissions['sysop']['editcontentmodel'] = true;
 $wgGroupPermissions['sysop']['import'] = true;
 $wgGroupPermissions['sysop']['importupload'] = true;
 $wgGroupPermissions['sysop']['move'] = true;
@@ -5580,6 +5581,11 @@ $wgRateLimits = [
                'ip' => [ 8, 60 ],
                'newbie' => [ 8, 60 ],
        ],
+       // Changing the content model of a page
+       'editcontentmodel' => [
+               'newbie' => [ 2, 120 ],
+               'user' => [ 8, 60 ],
+       ],
 ];
 
 /**
@@ -6162,6 +6168,14 @@ $wgStatsdServer = false;
  */
 $wgStatsdMetricPrefix = 'MediaWiki';
 
+/**
+ * Sampling rate for statsd metrics as an associative array of patterns and rates.
+ * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*').
+ * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled).
+ * @since 1.28
+ */
+$wgStatsdSamplingRates = [];
+
 /**
  * InfoAction retrieves a list of transclusion links (both to and from).
  * This number puts a limit on that query in the case of highly transcluded
@@ -7211,8 +7225,8 @@ $wgJobTypesExcludedFromDefaultQueue = [ 'AssembleUploadChunks', 'PublishStashedF
 $wgJobBackoffThrottling = [];
 
 /**
- * Make job runners commit changes for slave-lag prone jobs one job at a time.
- * This is useful if there are many job workers that race on slave lag checks.
+ * Make job runners commit changes for replica DB-lag prone jobs one job at a time.
+ * This is useful if there are many job workers that race on replica DB lag checks.
  * If set, jobs taking this many seconds of DB write time have serialized commits.
  *
  * Note that affected jobs may have worse lock contention. Also, if they affect
@@ -7834,7 +7848,7 @@ $wgAPIMaxResultSize = 8388608;
 $wgAPIMaxUncachedDiffs = 1;
 
 /**
- * Maximum amount of DB lag on a majority of DB slaves to tolerate
+ * Maximum amount of DB lag on a majority of DB replica DBs to tolerate
  * before forcing bots to retry any write requests via API errors.
  * This should be lower than the 'max lag' value in $wgLBFactoryConf.
  */
@@ -8266,7 +8280,7 @@ $wgPageLanguageUseDB = false;
  * Global configuration variable for Virtual REST Services.
  *
  * Use the 'path' key to define automatically mounted services. The value for this
- * key is a map of path prefixes to service configuration. The later is an array of:
+ * key is a map of path prefixes to service configuration. The latter is an array of:
  *   - class : the fully qualified class name
  *   - options : map of arguments to the class constructor
  * Such services will be available to handle queries under their path from the VRS
index fe5083e..ab02a8e 100644 (file)
@@ -43,13 +43,12 @@ define( 'DBO_COMPRESS', 512 );
  * Valid database indexes
  * Operation-based indexes
  */
-define( 'DB_SLAVE', -1 );     # Read from the slave (or only server)
+define( 'DB_REPLICA', -1 );     # Read from a replica (or only server)
 define( 'DB_MASTER', -2 );    # Write to master (or only server)
 /**@}*/
 
 # Obsolete aliases
-define( 'DB_READ', -1 );
-define( 'DB_WRITE', -2 );
+define( 'DB_SLAVE', -1 );
 
 /**@{
  * Virtual namespaces; don't appear in the page database
index e0080fa..7e4e411 100644 (file)
@@ -562,16 +562,29 @@ class EditPage {
 
                $revision = $this->mArticle->getRevisionFetched();
                // Disallow editing revisions with content models different from the current one
-               if ( $revision && $revision->getContentModel() !== $this->contentModel ) {
-                       $this->displayViewSourcePage(
-                               $this->getContentObject(),
-                               wfMessage(
-                                       'contentmodelediterror',
-                                       $revision->getContentModel(),
-                                       $this->contentModel
-                               )->plain()
-                       );
-                       return;
+               // Undo edits being an exception in order to allow reverting content model changes.
+               if ( $revision
+                       && $revision->getContentModel() !== $this->contentModel
+               ) {
+                       $prevRev = null;
+                       if ( $this->undidRev ) {
+                               $undidRevObj = Revision::newFromId( $this->undidRev );
+                               $prevRev = $undidRevObj ? $undidRevObj->getPrevious() : null;
+                       }
+                       if ( !$this->undidRev
+                               || !$prevRev
+                               || $prevRev->getContentModel() !== $this->contentModel
+                       ) {
+                               $this->displayViewSourcePage(
+                                       $this->getContentObject(),
+                                       wfMessage(
+                                               'contentmodelediterror',
+                                               $revision->getContentModel(),
+                                               $this->contentModel
+                                       )->plain()
+                               );
+                               return;
+                       }
                }
 
                $this->isConflict = false;
@@ -1134,6 +1147,14 @@ class EditPage {
                                                        $oldContent = $this->page->getContent( Revision::RAW );
                                                        $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
                                                        $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
+                                                       if ( $newContent->getModel() !== $oldContent->getModel() ) {
+                                                               // The undo may change content
+                                                               // model if its reverting the top
+                                                               // edit. This can result in
+                                                               // mismatched content model/format.
+                                                               $this->contentModel = $newContent->getModel();
+                                                               $this->contentFormat = $oldrev->getContentFormat();
+                                                       }
 
                                                        if ( $newContent->equals( $oldContent ) ) {
                                                                # Tell the user that the undo results in no change,
@@ -1264,9 +1285,11 @@ class EditPage {
                        $handler = ContentHandler::getForModelID( $this->contentModel );
 
                        return $handler->makeEmptyContent();
-               } else {
+               } elseif ( !$this->undidRev ) {
                        // Content models should always be the same since we error
-                       // out if they are different before this point.
+                       // out if they are different before this point (in ->edit()).
+                       // The exception being, during an undo, the current revision might
+                       // differ from the prior revision.
                        $logger = LoggerFactory::getInstance( 'editpage' );
                        if ( $this->contentModel !== $rev->getContentModel() ) {
                                $logger->warning( "Overriding content model from current edit {prev} to {new}", [
@@ -1290,9 +1313,8 @@ class EditPage {
                                ] );
                                $this->contentFormat = $rev->getContentFormat();
                        }
-
-                       return $content;
                }
+               return $content;
        }
 
        /**
@@ -1836,7 +1858,9 @@ class EditPage {
                        $status->value = self::AS_READ_ONLY_PAGE;
                        return $status;
                }
-               if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
+               if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 )
+                       || ( $changingContentModel && $wgUser->pingLimiter( 'editcontentmodel' ) )
+               ) {
                        $status->fatal( 'actionthrottledtext' );
                        $status->value = self::AS_RATE_LIMITED;
                        return $status;
@@ -3613,7 +3637,7 @@ HTML
         * @return bool|stdClass
         */
        protected function getLastDelete() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $data = $dbr->selectRow(
                        [ 'logging', 'user' ],
                        [
index 158c852..3b2283b 100644 (file)
@@ -226,6 +226,7 @@ class FauxRequest extends WebRequest {
        }
 
        /**
+        * @codeCoverageIgnore
         * @param array $extWhitelist
         * @return bool
         */
@@ -234,6 +235,7 @@ class FauxRequest extends WebRequest {
        }
 
        /**
+        * @codeCoverageIgnore
         * @return string
         */
        protected function getRawIP() {
index 0d66908..bc78be5 100644 (file)
@@ -1195,6 +1195,7 @@ function wfLogProfilingData() {
                        $statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125;
                        $statsdSender = new SocketSender( $statsdHost, $statsdPort );
                        $statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
+                       $statsdClient->setSamplingRates( $config->get( 'StatsdSamplingRates' ) );
                        $statsdClient->send( $context->getStats()->getBuffer() );
                } catch ( Exception $ex ) {
                        MWExceptionHandler::logException( $ex );
@@ -1277,7 +1278,7 @@ function wfReadOnly() {
  * Check if the site is in read-only mode and return the message if so
  *
  * This checks wfConfiguredReadOnlyReason() and the main load balancer
- * for slave lag. This may result in DB_SLAVE connection being made.
+ * for replica DB lag. This may result in DB connection being made.
  *
  * @return string|bool String when in read-only mode; false otherwise
  */
@@ -3124,7 +3125,7 @@ function wfSplitWikiID( $wiki ) {
  * Get a Database object.
  *
  * @param int $db Index of the connection to get. May be DB_MASTER for the
- *            master (for write queries), DB_SLAVE for potentially lagged read
+ *            master (for write queries), DB_REPLICA for potentially lagged read
  *            queries, or an integer >= 0 for a particular server.
  *
  * @param string|string[] $groups Query groups. An array of group names that this query
@@ -3133,7 +3134,7 @@ function wfSplitWikiID( $wiki ) {
  *
  * @param string|bool $wiki The wiki ID, or false for the current wiki
  *
- * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
+ * Note: multiple calls to wfGetDB(DB_REPLICA) during the course of one request
  * will always return the same object, unless the underlying connection or load
  * balancer is manually destroyed.
  *
@@ -3278,10 +3279,10 @@ function wfGetNull() {
 }
 
 /**
- * Waits for the slaves to catch up to the master position
+ * Waits for the replica DBs to catch up to the master position
  *
  * Use this when updating very large numbers of rows, as in maintenance scripts,
- * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
+ * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
  *
  * By default this waits on the main DB cluster of the current wiki.
  * If $cluster is set to "*" it will wait on all DB clusters, including
index 8673125..b17a2f5 100644 (file)
@@ -245,7 +245,7 @@ class HistoryBlobStub {
                if ( isset( self::$blobCache[$this->mOldId] ) ) {
                        $obj = self::$blobCache[$this->mOldId];
                } else {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $row = $dbr->selectRow(
                                'text',
                                [ 'old_flags', 'old_text' ],
@@ -336,7 +336,7 @@ class HistoryBlobCurStub {
         * @return string|bool
         */
        function getText() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $row = $dbr->selectRow( 'cur', [ 'cur_text' ], [ 'cur_id' => $this->mCurId ] );
                if ( !$row ) {
                        return false;
index 802bfbe..7b3d72b 100644 (file)
@@ -89,10 +89,10 @@ class LinkFilter {
         *
         * @param string $filterEntry Domainparts
         * @param string $protocol Protocol (default http://)
-        * @return array Array to be passed to Database::buildLike() or false on error
+        * @return array|bool Array to be passed to Database::buildLike() or false on error
         */
        public static function makeLikeArray( $filterEntry, $protocol = 'http://' ) {
-               $db = wfGetDB( DB_SLAVE );
+               $db = wfGetDB( DB_REPLICA );
 
                $target = $protocol . $filterEntry;
                $bits = wfParseUrl( $target );
index f8b1bae..bcc348e 100644 (file)
@@ -1804,7 +1804,7 @@ class Linker {
                        return null;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Up to the value of $wgShowRollbackEditCount revisions are counted
                $res = $dbr->select(
index 1add24a..bca7a21 100644 (file)
@@ -554,9 +554,9 @@ class MediaWiki {
 
                $config = $context->getConfig();
 
-               $factory = wfGetLBFactory();
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                // Commit all changes
-               $factory->commitMasterChanges(
+               $lbFactory->commitMasterChanges(
                        __METHOD__,
                        // Abort if any transaction was too big
                        [ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ]
@@ -566,23 +566,23 @@ class MediaWiki {
                wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
 
                // Record ChronologyProtector positions
-               $factory->shutdown();
+               $lbFactory->shutdown();
                wfDebug( __METHOD__ . ': all transactions committed' );
 
                // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that handles this
                // POST request (e.g. the "master" data center). Also have the user briefly bypass CDN so
                // ChronologyProtector works for cacheable URLs.
                $request = $context->getRequest();
-               if ( $request->wasPosted() && $factory->hasOrMadeRecentMasterChanges() ) {
+               if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentMasterChanges() ) {
                        $expires = time() + $config->get( 'DataCenterUpdateStickTTL' );
                        $options = [ 'prefix' => '' ];
                        $request->response()->setCookie( 'UseDC', 'master', $expires, $options );
                        $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options );
                }
 
-               // Avoid letting a few seconds of slave lag cause a month of stale data. This logic is
+               // Avoid letting a few seconds of replica DB lag cause a month of stale data. This logic is
                // also intimately related to the value of $wgCdnReboundPurgeDelay.
-               if ( $factory->laggedSlaveUsed() ) {
+               if ( $lbFactory->laggedReplicaUsed() ) {
                        $maxAge = $config->get( 'CdnMaxageLagged' );
                        $context->getOutput()->lowerCdnMaxage( $maxAge );
                        $request->response()->header( "X-Database-Lagged: true" );
@@ -632,7 +632,7 @@ class MediaWiki {
                                fastcgi_finish_request();
                        } else {
                                // Either all DB and deferred updates should happen or none.
-                               // The later should not be cancelled due to client disconnect.
+                               // The latter should not be cancelled due to client disconnect.
                                ignore_user_abort( true );
                        }
 
@@ -763,9 +763,9 @@ class MediaWiki {
         * @param string $mode Use 'fast' to always skip job running
         */
        public function restInPeace( $mode = 'fast' ) {
-               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                // Assure deferred updates are not in the main transaction
-               $factory->commitMasterChanges( __METHOD__ );
+               $lbFactory->commitMasterChanges( __METHOD__ );
 
                // Loosen DB query expectations since the HTTP client is unblocked
                $trxProfiler = Profiler::instance()->getTransactionProfiler();
@@ -791,8 +791,8 @@ class MediaWiki {
                wfLogProfilingData();
 
                // Commit and close up!
-               $factory->commitMasterChanges( __METHOD__ );
-               $factory->shutdown( LBFactory::SHUTDOWN_NO_CHRONPROT );
+               $lbFactory->commitMasterChanges( __METHOD__ );
+               $lbFactory->shutdown( LBFactory::SHUTDOWN_NO_CHRONPROT );
 
                wfDebug( "Request ended normally\n" );
        }
index 441fe9e..dd1fd37 100644 (file)
@@ -33,7 +33,7 @@
  */
 class MergeHistory {
 
-       /** @const int Maximum number of revisions that can be merged at once (avoid too much slave lag) */
+       /** @const int Maximum number of revisions that can be merged at once */
        const REVISION_LIMIT = 5000;
 
        /** @var Title Page from which history will be merged */
index 1083687..9b2d8da 100644 (file)
@@ -1265,7 +1265,7 @@ class OutputPage extends ContextSource {
                $lb->setArray( $arr );
 
                # Fetch existence plus the hiddencat property
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $fields = array_merge(
                        LinkCache::getSelectFields(),
                        [ 'page_namespace', 'page_title', 'pp_value' ]
@@ -2303,7 +2303,6 @@ class OutputPage extends ContextSource {
                        // Hook that allows last minute changes to the output page, e.g.
                        // adding of CSS or Javascript by extensions.
                        Hooks::run( 'BeforePageDisplay', [ &$this, &$sk ] );
-                       $this->getSkin()->setupSkinUserCss( $this );
 
                        try {
                                $sk->outputPage();
@@ -2536,7 +2535,7 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Show a warning about slave lag
+        * Show a warning about replica DB lag
         *
         * If the lag is higher than $wgSlaveLagCritical seconds,
         * then the warning is a bit more obvious. If the lag is
@@ -2675,20 +2674,34 @@ class OutputPage extends ContextSource {
                                'user.styles',
                                'user.cssprefs',
                        ] );
+                       $this->getSkin()->setupSkinUserCss( $this );
 
                        // Prepare exempt modules for buildExemptModules()
                        $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
                        $exemptStates = [];
-                       $moduleStyles = array_filter( $this->getModuleStyles( /*filter*/ true ),
+                       $moduleStyles = $this->getModuleStyles( /*filter*/ true );
+
+                       // Batch preload getTitleInfo for isKnownEmpty() calls below
+                       $exemptModules = array_filter( $moduleStyles,
+                               function ( $name ) use ( $rl, &$exemptGroups ) {
+                                       $module = $rl->getModule( $name );
+                                       return $module && isset( $exemptGroups[ $module->getGroup() ] );
+                               }
+                       );
+                       ResourceLoaderWikiModule::preloadTitleInfo(
+                               $context, wfGetDB( DB_REPLICA ), $exemptModules );
+
+                       // Filter out modules handled by buildExemptModules()
+                       $moduleStyles = array_filter( $moduleStyles,
                                function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
                                        $module = $rl->getModule( $name );
                                        if ( $module ) {
-                                               $group = $module->getGroup();
                                                if ( $name === 'user.styles' && $this->isUserCssPreview() ) {
                                                        $exemptStates[$name] = 'ready';
                                                        // Special case in buildExemptModules()
                                                        return false;
                                                }
+                                               $group = $module->getGroup();
                                                if ( isset( $exemptGroups[$group] ) ) {
                                                        $exemptStates[$name] = 'ready';
                                                        if ( !$module->isKnownEmpty( $context ) ) {
index 5579ba5..ed06935 100644 (file)
@@ -140,7 +140,7 @@ class PageProps {
                }
 
                if ( $queryIDs ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $result = $dbr->select(
                                'page_props',
                                [
@@ -198,7 +198,7 @@ class PageProps {
                }
 
                if ( $queryIDs != [] ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $result = $dbr->select(
                                'page_props',
                                [
index 0039d2b..bd1b2a2 100644 (file)
@@ -72,7 +72,7 @@ class Pingback {
         * @return bool
         */
        private function checkIfSent() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $sent = $dbr->selectField(
                        'updatelog', '1', [ 'ul_key' => $this->key ], __METHOD__ );
                return $sent !== false;
@@ -168,7 +168,7 @@ class Pingback {
         */
        private function getOrCreatePingbackId() {
                if ( !$this->id ) {
-                       $id = wfGetDB( DB_SLAVE )->selectField(
+                       $id = wfGetDB( DB_REPLICA )->selectField(
                                'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ] );
 
                        if ( $id == false ) {
index 5a9f0b0..9f8c06b 100644 (file)
@@ -1594,7 +1594,7 @@ class PreferencesForm extends HTMLForm {
         * Get extra parameters for the query string when redirecting after
         * successful save.
         *
-        * @return array()
+        * @return array
         */
        public function getExtraSuccessRedirectParameters() {
                return [];
index 04c68ca..49e596d 100644 (file)
@@ -272,7 +272,7 @@ abstract class PrefixSearch {
 
                $t = Title::newFromText( $search, $ns );
                $prefix = $t ? $t->getDBkey() : '';
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select( 'page',
                        [ 'page_id', 'page_namespace', 'page_title' ],
                        [
index eda8989..6acc528 100644 (file)
@@ -25,52 +25,60 @@ use MediaWiki\Linker\LinkTarget;
  * @todo document
  */
 class Revision implements IDBAccessObject {
+       /** @var int|null */
        protected $mId;
-
-       /**
-        * @var int|null
-        */
+       /** @var int|null */
        protected $mPage;
+       /** @var string */
        protected $mUserText;
+       /** @var string */
        protected $mOrigUserText;
+       /** @var int */
        protected $mUser;
+       /** @var bool */
        protected $mMinorEdit;
+       /** @var string */
        protected $mTimestamp;
+       /** @var int */
        protected $mDeleted;
+       /** @var int */
        protected $mSize;
+       /** @var string */
        protected $mSha1;
+       /** @var int */
        protected $mParentId;
+       /** @var string */
        protected $mComment;
+       /** @var string */
        protected $mText;
+       /** @var int */
        protected $mTextId;
+       /** @var int */
+       protected $mUnpatrolled;
 
-       /**
-        * @var stdClass|null
-        */
+       /** @var stdClass|null */
        protected $mTextRow;
 
-       /**
-        * @var null|Title
-        */
+       /**  @var null|Title */
        protected $mTitle;
+       /** @var bool */
        protected $mCurrent;
+       /** @var string */
        protected $mContentModel;
+       /** @var string */
        protected $mContentFormat;
 
-       /**
-        * @var Content|null|bool
-        */
+       /** @var Content|null|bool */
        protected $mContent;
-
-       /**
-        * @var null|ContentHandler
-        */
+       /** @var null|ContentHandler */
        protected $mContentHandler;
 
-       /**
-        * @var int
-        */
+       /** @var int */
        protected $mQueryFlags = 0;
+       /** @var bool Used for cached values to reload user text and rev_deleted */
+       protected $mRefreshMutableFields = false;
+       /** @var string Wiki ID; false means the current wiki */
+       protected $mWiki = false;
 
        // Revision deletion constants
        const DELETED_TEXT = 1;
@@ -84,6 +92,8 @@ class Revision implements IDBAccessObject {
        const FOR_THIS_USER = 2;
        const RAW = 3;
 
+       const TEXT_CACHE_GROUP = 'revisiontext:10'; // process cache name and max key count
+
        /**
         * Load a page revision from a given revision ID number.
         * Returns null if no such revision can be found.
@@ -126,7 +136,7 @@ class Revision implements IDBAccessObject {
                } else {
                        // Use a join to get the latest revision
                        $conds[] = 'rev_id=page_latest';
-                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
                        return self::loadFromConds( $db, $conds, $flags );
                }
        }
@@ -153,7 +163,7 @@ class Revision implements IDBAccessObject {
                } else {
                        // Use a join to get the latest revision
                        $conds[] = 'rev_id = page_latest';
-                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+                       $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
                        return self::loadFromConds( $db, $conds, $flags );
                }
        }
@@ -301,14 +311,14 @@ class Revision implements IDBAccessObject {
         * Given a set of conditions, fetch a revision
         *
         * This method is used then a revision ID is qualified and
-        * will incorporate some basic slave/master fallback logic
+        * will incorporate some basic replica DB/master fallback logic
         *
         * @param array $conditions
         * @param int $flags (optional)
         * @return Revision|null
         */
        private static function newFromConds( $conditions, $flags = 0 ) {
-               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
 
                $rev = self::loadFromConds( $db, $conditions, $flags );
                // Make sure new pending/committed revision are visibile later on
@@ -340,16 +350,15 @@ class Revision implements IDBAccessObject {
         * @return Revision|null
         */
        private static function loadFromConds( $db, $conditions, $flags = 0 ) {
-               $res = self::fetchFromConds( $db, $conditions, $flags );
-               if ( $res ) {
-                       $row = $res->fetchObject();
-                       if ( $row ) {
-                               $ret = new Revision( $row );
-                               return $ret;
-                       }
+               $row = self::fetchFromConds( $db, $conditions, $flags );
+               if ( $row ) {
+                       $rev = new Revision( $row );
+                       $rev->mWiki = $db->getWikiID();
+
+                       return $rev;
                }
-               $ret = null;
-               return $ret;
+
+               return null;
        }
 
        /**
@@ -357,18 +366,21 @@ class Revision implements IDBAccessObject {
         * fetch all of a given page's revisions in turn.
         * Each row can be fed to the constructor to get objects.
         *
-        * @param Title $title
+        * @param LinkTarget $title
         * @return ResultWrapper
+        * @deprecated Since 1.28
         */
-       public static function fetchRevision( $title ) {
-               return self::fetchFromConds(
-                       wfGetDB( DB_SLAVE ),
+       public static function fetchRevision( LinkTarget $title ) {
+               $row = self::fetchFromConds(
+                       wfGetDB( DB_REPLICA ),
                        [
                                'rev_id=page_latest',
                                'page_namespace' => $title->getNamespace(),
                                'page_title' => $title->getDBkey()
                        ]
                );
+
+               return new FakeResultWrapper( $row ? [ $row ] : [] );
        }
 
        /**
@@ -379,7 +391,7 @@ class Revision implements IDBAccessObject {
         * @param IDatabase $db
         * @param array $conditions
         * @param int $flags (optional)
-        * @return ResultWrapper
+        * @return stdClass
         */
        private static function fetchFromConds( $db, $conditions, $flags = 0 ) {
                $fields = array_merge(
@@ -387,11 +399,11 @@ class Revision implements IDBAccessObject {
                        self::selectPageFields(),
                        self::selectUserFields()
                );
-               $options = [ 'LIMIT' => 1 ];
+               $options = [];
                if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) {
                        $options[] = 'FOR UPDATE';
                }
-               return $db->select(
+               return $db->selectRow(
                        [ 'revision', 'page', 'user' ],
                        $fields,
                        $conditions,
@@ -793,20 +805,24 @@ class Revision implements IDBAccessObject {
                }
                // rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
                if ( $this->mId !== null ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
                        $row = $dbr->selectRow(
                                [ 'page', 'revision' ],
                                self::selectPageFields(),
-                               [ 'page_id=rev_page',
-                                       'rev_id' => $this->mId ],
-                               __METHOD__ );
+                               [ 'page_id=rev_page', 'rev_id' => $this->mId ],
+                               __METHOD__
+                       );
                        if ( $row ) {
+                               // @TODO: better foreign title handling
                                $this->mTitle = Title::newFromRow( $row );
                        }
                }
 
-               if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
-                       $this->mTitle = Title::newFromID( $this->mPage );
+               if ( $this->mWiki === false || $this->mWiki === wfWikiID() ) {
+                       // Loading by ID is best, though not possible for foreign titles
+                       if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) {
+                               $this->mTitle = Title::newFromID( $this->mPage );
+                       }
                }
 
                return $this->mTitle;
@@ -878,6 +894,8 @@ class Revision implements IDBAccessObject {
         * @return string
         */
        public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
+               $this->loadMutableFields();
+
                if ( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
                        return '';
                } elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
@@ -973,7 +991,7 @@ class Revision implements IDBAccessObject {
         * @return RecentChange|null
         */
        public function getRecentChange( $flags = 0 ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                list( $dbType, ) = DBAccessObjectUtils::getDBOptions( $flags );
 
@@ -994,7 +1012,14 @@ class Revision implements IDBAccessObject {
         * @return bool
         */
        public function isDeleted( $field ) {
-               return ( $this->mDeleted & $field ) == $field;
+               if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
+                       // Current revisions of pages cannot have the content hidden. Skipping this
+                       // check is very useful for Parser as it fetches templates using newKnownCurrent().
+                       // Calling getVisibility() in that case triggers a verification database query.
+                       return false; // no need to check
+               }
+
+               return ( $this->getVisibility() & $field ) == $field;
        }
 
        /**
@@ -1003,6 +1028,8 @@ class Revision implements IDBAccessObject {
         * @return int
         */
        public function getVisibility() {
+               $this->loadMutableFields();
+
                return (int)$this->mDeleted;
        }
 
@@ -1054,13 +1081,14 @@ class Revision implements IDBAccessObject {
        }
 
        /**
-        * Fetch original serialized data without regard for view restrictions
+        * Get original serialized data (without checking view restrictions)
         *
         * @since 1.21
         * @return string
         */
        public function getSerializedData() {
                if ( $this->mText === null ) {
+                       // Revision is immutable. Load on demand.
                        $this->mText = $this->loadText();
                }
 
@@ -1078,17 +1106,14 @@ class Revision implements IDBAccessObject {
         */
        protected function getContentInternal() {
                if ( $this->mContent === null ) {
-                       // Revision is immutable. Load on demand:
-                       if ( $this->mText === null ) {
-                               $this->mText = $this->loadText();
-                       }
+                       $text = $this->getSerializedData();
 
-                       if ( $this->mText !== null && $this->mText !== false ) {
+                       if ( $text !== null && $text !== false ) {
                                // Unserialize content
                                $handler = $this->getContentHandler();
                                $format = $this->getContentFormat();
 
-                               $this->mContent = $handler->unserializeContent( $this->mText, $format );
+                               $this->mContent = $handler->unserializeContent( $text, $format );
                        }
                }
 
@@ -1551,29 +1576,30 @@ class Revision implements IDBAccessObject {
         *
         * @return string|bool The revision's text, or false on failure
         */
-       protected function loadText() {
-               // Caching may be beneficial for massive use of external storage
+       private function loadText() {
                global $wgRevisionCacheExpiry;
-               static $processCache = null;
 
-               if ( !$processCache ) {
-                       $processCache = new MapCacheLRU( 10 );
+               $cache = ObjectCache::getMainWANInstance();
+               if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= $cache::QOS_EMULATION_SQL ) {
+                       // Do not cache RDBMs blobs in...the RDBMs store
+                       $ttl = $cache::TTL_UNCACHEABLE;
+               } else {
+                       $ttl = $wgRevisionCacheExpiry ?: $cache::TTL_UNCACHEABLE;
                }
 
-               $cache = ObjectCache::getMainWANInstance();
-               $textId = $this->getTextId();
-               $key = wfMemcKey( 'revisiontext', 'textid', $textId );
+               // No negative caching; negative hits on text rows may be due to corrupted replica DBs
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
+                       $ttl,
+                       function () {
+                               return $this->fetchText();
+                       },
+                       [ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => $cache::TTL_PROC_LONG ]
+               );
+       }
 
-               if ( $wgRevisionCacheExpiry ) {
-                       if ( $processCache->has( $key ) ) {
-                               return $processCache->get( $key );
-                       }
-                       $text = $cache->get( $key );
-                       if ( is_string( $text ) ) {
-                               $processCache->set( $key, $text );
-                               return $text;
-                       }
-               }
+       private function fetchText() {
+               $textId = $this->getTextId();
 
                // If we kept data for lazy extraction, use it now...
                if ( $this->mTextRow !== null ) {
@@ -1583,25 +1609,38 @@ class Revision implements IDBAccessObject {
                        $row = null;
                }
 
+               // Callers doing updates will pass in READ_LATEST as usual. Since the text/blob tables
+               // do not normally get rows changed around, set READ_LATEST_IMMUTABLE in those cases.
+               $flags = $this->mQueryFlags;
+               $flags |= DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST )
+                       ? self::READ_LATEST_IMMUTABLE
+                       : 0;
+
+               list( $index, $options, $fallbackIndex, $fallbackOptions ) =
+                       DBAccessObjectUtils::getDBOptions( $flags );
+
                if ( !$row ) {
-                       // Text data is immutable; check slaves first.
-                       $dbr = wfGetDB( DB_SLAVE );
-                       $row = $dbr->selectRow( 'text',
+                       // Text data is immutable; check replica DBs first.
+                       $row = wfGetDB( $index )->selectRow(
+                               'text',
                                [ 'old_text', 'old_flags' ],
                                [ 'old_id' => $textId ],
-                               __METHOD__ );
+                               __METHOD__,
+                               $options
+                       );
                }
 
-               // Fallback to the master in case of slave lag. Also use FOR UPDATE if it was
-               // used to fetch this revision to avoid missing the row due to REPEATABLE-READ.
-               $forUpdate = ( $this->mQueryFlags & self::READ_LOCKING == self::READ_LOCKING );
-               if ( !$row && ( $forUpdate || wfGetLB()->getServerCount() > 1 ) ) {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $row = $dbw->selectRow( 'text',
+               // Fallback to DB_MASTER in some cases if the row was not found
+               if ( !$row && $fallbackIndex !== null ) {
+                       // Use FOR UPDATE if it was used to fetch this revision. This avoids missing the row
+                       // due to REPEATABLE-READ. Also fallback to the master if READ_LATEST is provided.
+                       $row = wfGetDB( $fallbackIndex )->selectRow(
+                               'text',
                                [ 'old_text', 'old_flags' ],
                                [ 'old_id' => $textId ],
                                __METHOD__,
-                               $forUpdate ? [ 'FOR UPDATE' ] : [] );
+                               $fallbackOptions
+                       );
                }
 
                if ( !$row ) {
@@ -1613,13 +1652,7 @@ class Revision implements IDBAccessObject {
                        wfDebugLog( 'Revision', "No blob for text row '$textId' (revision {$this->getId()})." );
                }
 
-               # No negative caching -- negative hits on text rows may be due to corrupted slave servers
-               if ( $wgRevisionCacheExpiry && $text !== false ) {
-                       $processCache->set( $key, $text );
-                       $cache->set( $key, $text, $wgRevisionCacheExpiry );
-               }
-
-               return $text;
+               return is_string( $text ) ? $text : false;
        }
 
        /**
@@ -1706,7 +1739,7 @@ class Revision implements IDBAccessObject {
         * @return bool
         */
        public function userCan( $field, User $user = null ) {
-               return self::userCanBitfield( $this->mDeleted, $field, $user );
+               return self::userCanBitfield( $this->getVisibility(), $field, $user );
        }
 
        /**
@@ -1767,7 +1800,7 @@ class Revision implements IDBAccessObject {
        static function getTimestampFromId( $title, $id, $flags = 0 ) {
                $db = ( $flags & self::READ_LATEST )
                        ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
+                       : wfGetDB( DB_REPLICA );
                // Casting fix for databases that can't take '' for rev_id
                if ( $id == '' ) {
                        $id = 0;
@@ -1850,4 +1883,60 @@ class Revision implements IDBAccessObject {
                }
                return true;
        }
+
+       /**
+        * Load a revision based on a known page ID and current revision ID from the DB
+        *
+        * This method allows for the use of caching, though accessing anything that normally
+        * requires permission checks (aside from the text) will trigger a small DB lookup.
+        * The title will also be lazy loaded, though setTitle() can be used to preload it.
+        *
+        * @param IDatabase $db
+        * @param int $pageId Page ID
+        * @param int $revId Known current revision of this page
+        * @return Revision|bool Returns false if missing
+        * @since 1.28
+        */
+       public static function newKnownCurrent( IDatabase $db, $pageId, $revId ) {
+               $cache = ObjectCache::getMainWANInstance();
+               return $cache->getWithSetCallback(
+                       // Page/rev IDs passed in from DB to reflect history merges
+                       $cache->makeGlobalKey( 'revision', $db->getWikiID(), $pageId, $revId ),
+                       $cache::TTL_WEEK,
+                       function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
+                               $setOpts += Database::getCacheSetOptions( $db );
+
+                               $rev = Revision::loadFromPageId( $db, $pageId, $revId );
+                               // Reflect revision deletion and user renames
+                               if ( $rev ) {
+                                       $rev->mTitle = null; // mutable; lazy-load
+                                       $rev->mRefreshMutableFields = true;
+                               }
+
+                               return $rev ?: false; // don't cache negatives
+                       }
+               );
+       }
+
+       /**
+        * For cached revisions, make sure the user name and rev_deleted is up-to-date
+        */
+       private function loadMutableFields() {
+               if ( !$this->mRefreshMutableFields ) {
+                       return; // not needed
+               }
+
+               $this->mRefreshMutableFields = false;
+               $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki );
+               $row = $dbr->selectRow(
+                       [ 'revision', 'user' ],
+                       [ 'rev_deleted', 'user_name' ],
+                       [ 'rev_id' => $this->mId, 'user_id = rev_user' ],
+                       __METHOD__
+               );
+               if ( $row ) { // update values
+                       $this->mDeleted = (int)$row->rev_deleted;
+                       $this->mUserText = $row->user_name;
+               }
+       }
 }
index 811870c..fb444bd 100644 (file)
@@ -81,7 +81,7 @@ abstract class RevisionListBase extends ContextSource implements Iterator {
         */
        public function reset() {
                if ( !$this->res ) {
-                       $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
+                       $this->res = $this->doQuery( wfGetDB( DB_REPLICA ) );
                } else {
                        $this->res->rewind();
                }
index 33569e6..8734bd6 100644 (file)
@@ -166,7 +166,8 @@ return [
 
        'LinkCache' => function( MediaWikiServices $services ) {
                return new LinkCache(
-                       $services->getTitleFormatter()
+                       $services->getTitleFormatter(),
+                       ObjectCache::getMainWANInstance()
                );
        },
 
index 604ab93..ff7875c 100644 (file)
@@ -59,7 +59,7 @@ class SiteStats {
                        # Update schema
                        $u = new SiteStatsUpdate( 0, 0, 0 );
                        $u->doUpdate();
-                       self::$row = self::doLoad( wfGetDB( DB_SLAVE ) );
+                       self::$row = self::doLoad( wfGetDB( DB_REPLICA ) );
                }
 
                self::$loaded = true;
@@ -71,12 +71,12 @@ class SiteStats {
        static function loadAndLazyInit() {
                global $wgMiserMode;
 
-               wfDebug( __METHOD__ . ": reading site_stats from slave\n" );
-               $row = self::doLoad( wfGetDB( DB_SLAVE ) );
+               wfDebug( __METHOD__ . ": reading site_stats from replica DB\n" );
+               $row = self::doLoad( wfGetDB( DB_REPLICA ) );
 
                if ( !self::isSane( $row ) ) {
                        // Might have just been initialized during this request? Underflow?
-                       wfDebug( __METHOD__ . ": site_stats damaged or missing on slave\n" );
+                       wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" );
                        $row = self::doLoad( wfGetDB( DB_MASTER ) );
                }
 
@@ -87,7 +87,7 @@ class SiteStats {
                        // clean schema with mwdumper.
                        wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" );
 
-                       SiteStatsInit::doAllAndCommit( wfGetDB( DB_SLAVE ) );
+                       SiteStatsInit::doAllAndCommit( wfGetDB( DB_REPLICA ) );
 
                        $row = self::doLoad( wfGetDB( DB_MASTER ) );
                }
@@ -186,7 +186,7 @@ class SiteStats {
                        wfMemcKey( 'SiteStats', 'groupcounts', $group ),
                        $cache::TTL_HOUR,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $group ) {
-                               $dbr = wfGetDB( DB_SLAVE );
+                               $dbr = wfGetDB( DB_REPLICA );
 
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
@@ -229,7 +229,7 @@ class SiteStats {
         */
        static function pagesInNs( $ns ) {
                if ( !isset( self::$pageCount[$ns] ) ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        self::$pageCount[$ns] = (int)$dbr->selectField(
                                'page',
                                'COUNT(*)',
@@ -296,7 +296,7 @@ class SiteStatsInit {
                } elseif ( $database ) {
                        $this->db = wfGetDB( DB_MASTER );
                } else {
-                       $this->db = wfGetDB( DB_SLAVE, 'vslow' );
+                       $this->db = wfGetDB( DB_REPLICA, 'vslow' );
                }
        }
 
index 2021e0a..3475b26 100644 (file)
@@ -394,7 +394,7 @@ class Title implements LinkTarget {
         * @return Title|null The new object, or null on an error
         */
        public static function newFromID( $id, $flags = 0 ) {
-               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
                $row = $db->selectRow(
                        'page',
                        self::getSelectFields(),
@@ -419,7 +419,7 @@ class Title implements LinkTarget {
                if ( !count( $ids ) ) {
                        return [];
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $res = $dbr->select(
                        'page',
@@ -561,7 +561,7 @@ class Title implements LinkTarget {
         * @return Title|null An object representing the article, or null if no such article was found
         */
        public static function nameOf( $id ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $s = $dbr->selectRow(
                        'page',
@@ -751,12 +751,12 @@ class Title implements LinkTarget {
        /**
         * Callback for usort() to do title sorts by (namespace, title)
         *
-        * @param Title $a
-        * @param Title $b
+        * @param LinkTarget $a
+        * @param LinkTarget $b
         *
         * @return int Result of string comparison, or namespace comparison
         */
-       public static function compare( $a, $b ) {
+       public static function compare( LinkTarget $a, LinkTarget $b ) {
                if ( $a->getNamespace() == $b->getNamespace() ) {
                        return strcmp( $a->getText(), $b->getText() );
                } else {
@@ -1886,8 +1886,8 @@ class Title implements LinkTarget {
         * @param string $action Action that permission needs to be checked for
         * @param User $user User to check
         * @param string $rigor One of (quick,full,secure)
-        *   - quick  : does cheap permission checks from slaves (usable for GUI creation)
-        *   - full   : does cheap and expensive checks possibly from a slave
+        *   - quick  : does cheap permission checks from replica DBs (usable for GUI creation)
+        *   - full   : does cheap and expensive checks possibly from a replica DB
         *   - secure : does cheap and expensive checks, using the master as needed
         * @param array $ignoreErrors Array of Strings Set this to a list of message keys
         *   whose corresponding errors may be ignored.
@@ -2416,8 +2416,8 @@ class Title implements LinkTarget {
         * @param string $action Action that permission needs to be checked for
         * @param User $user User to check
         * @param string $rigor One of (quick,full,secure)
-        *   - quick  : does cheap permission checks from slaves (usable for GUI creation)
-        *   - full   : does cheap and expensive checks possibly from a slave
+        *   - quick  : does cheap permission checks from replica DBs (usable for GUI creation)
+        *   - full   : does cheap and expensive checks possibly from a replica DB
         *   - secure : does cheap and expensive checks, using the master as needed
         * @param bool $short Set this to true to stop after the first permission error.
         * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
@@ -2540,7 +2540,7 @@ class Title implements LinkTarget {
                }
 
                if ( $this->mTitleProtection === null ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $res = $dbr->select(
                                'protected_titles',
                                [
@@ -2708,7 +2708,7 @@ class Title implements LinkTarget {
                        return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                if ( $this->getNamespace() == NS_FILE ) {
                        $tables = [ 'imagelinks', 'page_restrictions' ];
@@ -2875,7 +2875,7 @@ class Title implements LinkTarget {
         *   restrictions from page table (pre 1.10)
         */
        public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $restrictionTypes = $this->getRestrictionTypes();
 
@@ -2949,7 +2949,7 @@ class Title implements LinkTarget {
         */
        public function loadRestrictions( $oldFashionedRestrictions = null ) {
                if ( !$this->mRestrictionsLoaded ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        if ( $this->exists() ) {
                                $res = $dbr->select(
                                        'page_restrictions',
@@ -3069,7 +3069,7 @@ class Title implements LinkTarget {
                        return [];
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $conds['page_namespace'] = $this->getNamespace();
                $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
                $options = [];
@@ -3096,7 +3096,7 @@ class Title implements LinkTarget {
                if ( $this->getNamespace() < 0 ) {
                        $n = 0;
                } else {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
 
                        $n = $dbr->selectField( 'archive', 'COUNT(*)',
                                [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
@@ -3121,7 +3121,7 @@ class Title implements LinkTarget {
                if ( $this->getNamespace() < 0 ) {
                        return false;
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $deleted = (bool)$dbr->selectField( 'archive', '1',
                        [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
                        __METHOD__
@@ -3375,7 +3375,7 @@ class Title implements LinkTarget {
                if ( count( $options ) > 0 ) {
                        $db = wfGetDB( DB_MASTER );
                } else {
-                       $db = wfGetDB( DB_SLAVE );
+                       $db = wfGetDB( DB_REPLICA );
                }
 
                $res = $db->select(
@@ -3437,7 +3437,7 @@ class Title implements LinkTarget {
                        return [];
                }
 
-               $db = wfGetDB( DB_SLAVE );
+               $db = wfGetDB( DB_REPLICA );
 
                $blNamespace = "{$prefix}_namespace";
                $blTitle = "{$prefix}_title";
@@ -3500,7 +3500,7 @@ class Title implements LinkTarget {
                        return [];
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select(
                        [ 'page', 'pagelinks' ],
                        [ 'pl_namespace', 'pl_title' ],
@@ -3860,7 +3860,7 @@ class Title implements LinkTarget {
                        return $data;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $res = $dbr->select(
                        'categorylinks',
@@ -3928,7 +3928,7 @@ class Title implements LinkTarget {
         * @return int|bool Old revision ID, or false if none exists
         */
        public function getPreviousRevisionID( $revId, $flags = 0 ) {
-               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
                $revId = $db->selectField( 'revision', 'rev_id',
                        [
                                'rev_page' => $this->getArticleID( $flags ),
@@ -3953,7 +3953,7 @@ class Title implements LinkTarget {
         * @return int|bool Next revision ID, or false if none exists
         */
        public function getNextRevisionID( $revId, $flags = 0 ) {
-               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
                $revId = $db->selectField( 'revision', 'rev_id',
                        [
                                'rev_page' => $this->getArticleID( $flags ),
@@ -3979,7 +3979,7 @@ class Title implements LinkTarget {
        public function getFirstRevision( $flags = 0 ) {
                $pageId = $this->getArticleID( $flags );
                if ( $pageId ) {
-                       $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+                       $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
                        $row = $db->selectRow( 'revision', Revision::selectFields(),
                                [ 'rev_page' => $pageId ],
                                __METHOD__,
@@ -4009,7 +4009,7 @@ class Title implements LinkTarget {
         * @return bool
         */
        public function isNewPage() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
        }
 
@@ -4026,7 +4026,7 @@ class Title implements LinkTarget {
                }
 
                if ( $this->mIsBigDeletion === null ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
 
                        $revCount = $dbr->selectRowCount(
                                'revision',
@@ -4053,7 +4053,7 @@ class Title implements LinkTarget {
                }
 
                if ( $this->mEstimateRevisions === null ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
                                [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
                }
@@ -4080,7 +4080,7 @@ class Title implements LinkTarget {
                if ( !$old || !$new ) {
                        return 0; // nothing to compare
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $conds = [
                        'rev_page' => $this->getArticleID(),
                        'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
@@ -4158,7 +4158,7 @@ class Title implements LinkTarget {
                        }
                        return $authors;
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
                        [
                                'rev_page' => $this->getArticleID(),
@@ -4385,6 +4385,7 @@ class Title implements LinkTarget {
                                                $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
                                                $fname
                                        );
+                                       MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
                                }
                        ),
                        DeferredUpdates::PRESEND
@@ -4413,7 +4414,7 @@ class Title implements LinkTarget {
         */
        public function getTouched( $db = null ) {
                if ( $db === null ) {
-                       $db = wfGetDB( DB_SLAVE );
+                       $db = wfGetDB( DB_REPLICA );
                }
                $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
                return $touched;
@@ -4497,7 +4498,7 @@ class Title implements LinkTarget {
        public function getRedirectsHere( $ns = null ) {
                $redirs = [];
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $where = [
                        'rd_namespace' => $this->getNamespace(),
                        'rd_title' => $this->getDBkey(),
index 3dcd30f..4ac4dea 100644 (file)
@@ -59,7 +59,7 @@ class WatchedItemQueryService {
         * @throws MWException
         */
        private function getConnection() {
-               return $this->loadBalancer->getConnection( DB_SLAVE, [ 'watchlist' ] );
+               return $this->loadBalancer->getConnection( DB_REPLICA, [ 'watchlist' ] );
        }
 
        /**
index a13609b..9a74401 100644 (file)
@@ -190,13 +190,13 @@ class WatchedItemStore implements StatsdAwareInterface {
        }
 
        /**
-        * @param int $slaveOrMaster DB_MASTER or DB_SLAVE
+        * @param int $dbIndex DB_MASTER or DB_REPLICA
         *
         * @return DatabaseBase
         * @throws MWException
         */
-       private function getConnection( $slaveOrMaster ) {
-               return $this->loadBalancer->getConnection( $slaveOrMaster, [ 'watchlist' ] );
+       private function getConnection( $dbIndex ) {
+               return $this->loadBalancer->getConnection( $dbIndex, [ 'watchlist' ] );
        }
 
        /**
@@ -217,7 +217,7 @@ class WatchedItemStore implements StatsdAwareInterface {
         * @return int
         */
        public function countWatchedItems( User $user ) {
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
                $return = (int)$dbr->selectField(
                        'watchlist',
                        'COUNT(*)',
@@ -237,7 +237,7 @@ class WatchedItemStore implements StatsdAwareInterface {
         * @return int
         */
        public function countWatchers( LinkTarget $target ) {
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
                $return = (int)$dbr->selectField(
                        'watchlist',
                        'COUNT(*)',
@@ -263,7 +263,7 @@ class WatchedItemStore implements StatsdAwareInterface {
         * @throws MWException
         */
        public function countVisitingWatchers( LinkTarget $target, $threshold ) {
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
                $visitingWatchers = (int)$dbr->selectField(
                        'watchlist',
                        'COUNT(*)',
@@ -293,7 +293,7 @@ class WatchedItemStore implements StatsdAwareInterface {
        public function countWatchersMultiple( array $targets, array $options = [] ) {
                $dbOptions = [ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ] ];
 
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
 
                if ( array_key_exists( 'minimumWatchers', $options ) ) {
                        $dbOptions['HAVING'] = 'COUNT(*) >= ' . (int)$options['minimumWatchers'];
@@ -341,7 +341,7 @@ class WatchedItemStore implements StatsdAwareInterface {
                array $targetsWithVisitThresholds,
                $minimumWatchers = null
        ) {
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
 
                $conds = $this->getVisitingWatchersCondition( $dbr, $targetsWithVisitThresholds );
 
@@ -452,7 +452,7 @@ class WatchedItemStore implements StatsdAwareInterface {
                        return false;
                }
 
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
                $row = $dbr->selectRow(
                        'watchlist',
                        'wl_notificationtimestamp',
@@ -499,7 +499,7 @@ class WatchedItemStore implements StatsdAwareInterface {
                                "wl_title {$options['sort']}"
                        ];
                }
-               $db = $this->getConnection( $options['forWrite'] ? DB_MASTER : DB_SLAVE );
+               $db = $this->getConnection( $options['forWrite'] ? DB_MASTER : DB_REPLICA );
 
                $res = $db->select(
                        'watchlist',
@@ -569,7 +569,7 @@ class WatchedItemStore implements StatsdAwareInterface {
                        return $timestamps;
                }
 
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
 
                $lb = new LinkBatch( $targetsToLoad );
                $res = $dbr->select(
@@ -885,7 +885,7 @@ class WatchedItemStore implements StatsdAwareInterface {
                        $queryOptions['LIMIT'] = $unreadLimit;
                }
 
-               $dbr = $this->getConnection( DB_SLAVE );
+               $dbr = $this->getConnection( DB_REPLICA );
                $rowCount = $dbr->selectRowCount(
                        'watchlist',
                        '1',
index 5492737..8f78164 100644 (file)
@@ -83,6 +83,9 @@ class WebRequest {
        /** @var bool Whether this HTTP request is "safe" (even if it is an HTTP post) */
        protected $markedAsSafe = false;
 
+       /**
+        * @codeCoverageIgnore
+        */
        public function __construct() {
                $this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
                        ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
@@ -351,7 +354,7 @@ class WebRequest {
         * @return array|string Cleaned-up version of the given
         * @private
         */
-       function normalizeUnicode( $data ) {
+       public function normalizeUnicode( $data ) {
                if ( is_array( $data ) ) {
                        foreach ( $data as $key => $val ) {
                                $data[$key] = $this->normalizeUnicode( $val );
@@ -641,6 +644,7 @@ class WebRequest {
         * Get the values passed in the query string.
         * No transformation is performed on the values.
         *
+        * @codeCoverageIgnore
         * @return array
         */
        public function getQueryValues() {
@@ -651,6 +655,7 @@ class WebRequest {
         * Return the contents of the Query with no decoding. Use when you need to
         * know exactly what was sent, e.g. for an OAuth signature over the elements.
         *
+        * @codeCoverageIgnore
         * @return string
         */
        public function getRawQueryString() {
index 0df372e..41378fb 100644 (file)
@@ -234,7 +234,7 @@ class HistoryAction extends FormlessAction {
                        return new FakeResultWrapper( [] );
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                if ( $direction === self::DIR_PREV ) {
                        list( $dirs, $oper ) = [ "ASC", ">=" ];
index ea66900..abc7cb2 100644 (file)
@@ -202,6 +202,7 @@ class InfoAction extends FormlessAction {
                $title = $this->getTitle();
                $id = $title->getArticleID();
                $config = $this->context->getConfig();
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
 
                $pageCounts = $this->pageCounts( $this->page );
 
@@ -279,9 +280,18 @@ class InfoAction extends FormlessAction {
                        . ' ' . $this->msg( 'parentheses', $pageLang )->escaped() ];
 
                // Content model of the page
+               $modelHtml = htmlspecialchars( ContentHandler::getLocalizedName( $title->getContentModel() ) );
+               // If the user can change it, add a link to Special:ChangeContentModel
+               if ( $title->quickUserCan( 'editcontentmodel' ) ) {
+                       $modelHtml .= ' ' . $this->msg( 'parentheses' )->rawParams( $linkRenderer->makeLink(
+                               SpecialPage::getTitleValueFor( 'ChangeContentModel', $title->getPrefixedText() ),
+                               $this->msg( 'pageinfo-content-model-change' )->text()
+                       ) )->escaped();
+               }
+
                $pageInfo['header-basic'][] = [
                        $this->msg( 'pageinfo-content-model' ),
-                       htmlspecialchars( ContentHandler::getLocalizedName( $title->getContentModel() ) )
+                       $modelHtml
                ];
 
                // Search engine status
@@ -676,8 +686,8 @@ class InfoAction extends FormlessAction {
                                $title = $page->getTitle();
                                $id = $title->getArticleID();
 
-                               $dbr = wfGetDB( DB_SLAVE );
-                               $dbrWatchlist = wfGetDB( DB_SLAVE, 'watchlist' );
+                               $dbr = wfGetDB( DB_REPLICA );
+                               $dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
 
                                $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
 
index c800ce7..e466e65 100644 (file)
@@ -112,13 +112,19 @@ class RevertAction extends FormAction {
        public function onSubmit( $data ) {
                $this->useTransactionalTimeLimit();
 
-               $source = $this->page->getFile()->getArchiveVirtualUrl(
-                       $this->getRequest()->getText( 'oldimage' )
-               );
+               $old = $this->getRequest()->getText( 'oldimage' );
+               $localFile = $this->page->getFile();
+               $oldFile = OldLocalFile::newFromArchiveName( $this->getTitle(), $localFile->getRepo(), $old );
+
+               $source = $localFile->getArchiveVirtualUrl( $old );
                $comment = $data['comment'];
 
+               if ( $localFile->getSha1() === $oldFile->getSha1() ) {
+                       return Status::newFatal( 'filerevert-identical' );
+               }
+
                // TODO: Preserve file properties from database instead of reloading from file
-               return $this->page->getFile()->upload(
+               return $localFile->upload(
                        $source,
                        $comment,
                        $comment,
index 3dc611b..aa2858d 100644 (file)
@@ -54,7 +54,7 @@ class RollbackAction extends FormlessAction {
                $user = $this->getUser();
                $from = $request->getVal( 'from' );
                $rev = $this->page->getRevision();
-               if ( $from === null || $from === '' ) {
+               if ( $from === null ) {
                        throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
                }
                if ( !$rev ) {
index 55d2430..f763e45 100644 (file)
@@ -599,12 +599,12 @@ abstract class ApiBase extends ContextSource {
        }
 
        /**
-        * Gets a default slave database connection object
+        * Gets a default replica DB connection object
         * @return DatabaseBase
         */
        protected function getDB() {
                if ( !isset( $this->mSlaveDB ) ) {
-                       $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
+                       $this->mSlaveDB = wfGetDB( DB_REPLICA, 'api' );
                }
 
                return $this->mSlaveDB;
@@ -826,7 +826,7 @@ abstract class ApiBase extends ContextSource {
         * @param array $params
         * @param bool|string $load Whether load the object's state from the database:
         *        - false: don't load (if the pageid is given, it will still be loaded)
-        *        - 'fromdb': load from a slave database
+        *        - 'fromdb': load from a replica DB
         *        - 'fromdbmaster': load from the master database
         * @return WikiPage
         */
index 00daba9..ee9150c 100644 (file)
@@ -97,6 +97,7 @@ class ApiEditPage extends ApiBase {
                } else {
                        $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
                }
+               $contentModel = $contentHandler->getModelID();
 
                $name = $titleObj->getPrefixedDBkey();
                $model = $contentHandler->getModelID();
@@ -111,11 +112,11 @@ class ApiEditPage extends ApiBase {
                }
 
                if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
-                       $params['contentformat'] = $contentHandler->getDefaultFormat();
+                       $contentFormat = $contentHandler->getDefaultFormat();
+               } else {
+                       $contentFormat = $params['contentformat'];
                }
 
-               $contentFormat = $params['contentformat'];
-
                if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
 
                        $this->dieUsage( "The requested format $contentFormat is not supported for content model " .
@@ -265,9 +266,21 @@ class ApiEditPage extends ApiBase {
                        if ( !$newContent ) {
                                $this->dieUsageMsg( 'undo-failure' );
                        }
-
-                       $params['text'] = $newContent->serialize( $params['contentformat'] );
-
+                       if ( empty( $params['contentmodel'] )
+                               && empty( $params['contentformat'] )
+                       ) {
+                               // If we are reverting content model, the new content model
+                               // might not support the current serialization format, in
+                               // which case go back to the old serialization format,
+                               // but only if the user hasn't specified a format/model
+                               // parameter.
+                               if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
+                                       $contentFormat = $undoafterRev->getContentFormat();
+                               }
+                               // Override content model with model of undid revision.
+                               $contentModel = $newContent->getModel();
+                       }
+                       $params['text'] = $newContent->serialize( $contentFormat );
                        // If no summary was given and we only undid one rev,
                        // use an autosummary
                        if ( is_null( $params['summary'] ) &&
@@ -288,7 +301,7 @@ class ApiEditPage extends ApiBase {
                $requestArray = [
                        'wpTextbox1' => $params['text'],
                        'format' => $contentFormat,
-                       'model' => $contentHandler->getModelID(),
+                       'model' => $contentModel,
                        'wpEditToken' => $params['token'],
                        'wpIgnoreBlankSummary' => true,
                        'wpIgnoreBlankArticle' => true,
index 48e7698..10fb182 100644 (file)
@@ -34,7 +34,7 @@
 class ApiExpandTemplates extends ApiBase {
 
        public function execute() {
-               // Cache may vary on $wgUser because ParserOptions gets data from it
+               // Cache may vary on the user because ParserOptions gets data from it
                $this->getMain()->setCacheMode( 'anon-public-user-private' );
 
                // Get parameters
index 22b079d..1f3c76a 100644 (file)
@@ -1322,9 +1322,9 @@ class ApiMain extends ApiBase {
                        }
                }
 
-               // If a majority of slaves are too lagged then disallow writes
-               $slaveCount = wfGetLB()->getServerCount() - 1;
-               if ( $numLagged >= ceil( $slaveCount / 2 ) ) {
+               // If a majority of replica DBs are too lagged then disallow writes
+               $replicaCount = wfGetLB()->getServerCount() - 1;
+               if ( $numLagged >= ceil( $replicaCount / 2 ) ) {
                        $laggedServers = implode( ', ', $laggedServers );
                        wfDebugLog(
                                'api-readonly',
index 1d250e9..661ec5a 100644 (file)
@@ -117,12 +117,12 @@ class ApiQueryAuthManagerInfo extends ApiQueryBase {
        protected function getExamplesMessages() {
                return [
                        'action=query&meta=authmanagerinfo&amirequestsfor=' . urlencode( AuthManager::ACTION_LOGIN )
-                               => 'apihelp-query+filerepoinfo-example-login',
+                               => 'apihelp-query+authmanagerinfo-example-login',
                        'action=query&meta=authmanagerinfo&amirequestsfor=' . urlencode( AuthManager::ACTION_LOGIN ) .
                                '&amimergerequestfields=1'
-                               => 'apihelp-query+filerepoinfo-example-login-merged',
+                               => 'apihelp-query+authmanagerinfo-example-login-merged',
                        'action=query&meta=authmanagerinfo&amisecuritysensitiveoperation=foo'
-                               => 'apihelp-query+filerepoinfo-example-securitysensitiveoperation',
+                               => 'apihelp-query+authmanagerinfo-example-securitysensitiveoperation',
                ];
        }
 
index 51e1923..f92a916 100644 (file)
@@ -57,13 +57,13 @@ class ApiQueryContributions extends ApiQueryBase {
                $this->fld_patrolled = isset( $prop['patrolled'] );
                $this->fld_tags = isset( $prop['tags'] );
 
-               // Most of this code will use the 'contributions' group DB, which can map to slaves
+               // Most of this code will use the 'contributions' group DB, which can map to replica DBs
                // with extra user based indexes or partioning by user. The additional metadata
-               // queries should use a regular slave since the lookup pattern is not all by user.
-               $dbSecondary = $this->getDB(); // any random slave
+               // queries should use a regular replica DB since the lookup pattern is not all by user.
+               $dbSecondary = $this->getDB(); // any random replica DB
 
                // TODO: if the query is going only against the revision table, should this be done?
-               $this->selectNamedDB( 'contributions', DB_SLAVE, 'contributions' );
+               $this->selectNamedDB( 'contributions', DB_REPLICA, 'contributions' );
 
                $this->idMode = false;
                if ( isset( $this->params['userprefix'] ) ) {
index e2599d1..c30f0cf 100644 (file)
@@ -57,7 +57,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
         * @return void
         */
        private function run( $resultPageSet = null ) {
-               $this->selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' );
+               $this->selectNamedDB( 'watchlist', DB_REPLICA, 'watchlist' );
 
                $params = $this->extractRequestParams();
 
index 297c0fb..5a2492d 100644 (file)
@@ -20,6 +20,7 @@
  */
 
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Prepare an edit in shared cache so that it can be reused on edit
@@ -62,6 +63,8 @@ class ApiStashEdit extends ApiBase {
                        $this->dieUsage( 'Unsupported content model/format', 'badmodelformat' );
                }
 
+               $text = null;
+               $textHash = null;
                if ( strlen( $params['stashedtexthash'] ) ) {
                        // Load from cache since the client indicates the text is the same as last stash
                        $textHash = $params['stashedtexthash'];
@@ -138,7 +141,8 @@ class ApiStashEdit extends ApiBase {
                        $cache->set( $textKey, $text, self::MAX_CACHE_TTL );
                }
 
-               $this->getStats()->increment( "editstash.cache_stores.$status" );
+               $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+               $stats->increment( "editstash.cache_stores.$status" );
 
                $this->getResult()->addValue(
                        null,
@@ -171,6 +175,7 @@ class ApiStashEdit extends ApiBase {
                        // De-duplicate requests on the same key
                        return self::ERROR_BUSY;
                }
+               /** @noinspection PhpUnusedLocalVariableInspection */
                $unlocker = new ScopedCallback( function () use ( $dbw, $key ) {
                        $dbw->unlock( $key, __METHOD__ );
                } );
@@ -246,7 +251,7 @@ class ApiStashEdit extends ApiBase {
 
                $cache = ObjectCache::getLocalClusterInstance();
                $logger = LoggerFactory::getInstance( 'StashEdit' );
-               $stats = RequestContext::getMain()->getStats();
+               $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
 
                $key = self::getStashKey( $title, self::getContentHash( $content ), $user );
                $editInfo = $cache->get( $key );
@@ -256,7 +261,7 @@ class ApiStashEdit extends ApiBase {
                        // so as to use its results and make use of the time spent parsing.
                        // Skip this logic if there no master connection in case this method
                        // is called on an HTTP GET request for some reason.
-                       $lb = wfGetLB();
+                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
                        $dbw = $lb->getAnyOpenConnection( $lb->getWriterIndex() );
                        if ( $dbw && $dbw->lock( $key, __METHOD__, 30 ) ) {
                                $editInfo = $cache->get( $key );
@@ -313,7 +318,7 @@ class ApiStashEdit extends ApiBase {
         * @return string|null TS_MW timestamp or null
         */
        private static function lastEditTime( User $user ) {
-               $time = wfGetDB( DB_SLAVE )->selectField(
+               $time = wfGetDB( DB_REPLICA )->selectField(
                        'recentchanges',
                        'MAX(rc_timestamp)',
                        [ 'rc_user_text' => $user->getName() ],
index c2d1e0d..f88c2db 100644 (file)
@@ -63,7 +63,7 @@ class ApiTag extends ApiBase {
        }
 
        protected static function validateLogId( $logid ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $result = $dbr->selectField( 'logging', 'log_id', [ 'log_id' => $logid ],
                        __METHOD__ );
                return (bool)$result;
index 54d3c3c..7b44f40 100644 (file)
@@ -704,6 +704,30 @@ class ApiUpload extends ApiBase {
                                        : $warning['file'];
                                $warnings[$warning['warning']] = $localFile->getName();
                        }
+
+                       if ( isset( $warnings['no-change'] ) ) {
+                               /** @var File $file */
+                               $file = $warnings['no-change'];
+                               unset( $warnings['no-change'] );
+
+                               $warnings['nochange'] = [
+                                       'timestamp' => wfTimestamp( TS_ISO_8601, $file->getTimestamp() )
+                               ];
+                       }
+
+                       if ( isset( $warnings['duplicate-version'] ) ) {
+                               $dupes = [];
+                               /** @var File $dupe */
+                               foreach ( $warnings['duplicate-version'] as $dupe ) {
+                                       $dupes[] = [
+                                               'timestamp' => wfTimestamp( TS_ISO_8601, $dupe->getTimestamp() )
+                                       ];
+                               }
+                               unset( $warnings['duplicate-version'] );
+
+                               ApiResult::setIndexedTagName( $dupes, 'ver' );
+                               $warnings['duplicateversions'] = $dupes;
+                       }
                }
 
                return $warnings;
index 06311f9..41f1ff9 100644 (file)
        "apihelp-query+allusers-param-witheditsonly": "Listet nur Benutzer auf, die Bearbeitungen vorgenommen haben.",
        "apihelp-query+allusers-param-activeusers": "Listet nur Benutzer auf, die in den letzten $1 {{PLURAL:$1|Tag|Tagen}} aktiv waren.",
        "apihelp-query+allusers-example-Y": "Benutzer ab <kbd>Y</kbd> auflisten.",
+       "apihelp-query+authmanagerinfo-example-login": "Ruft die Anfragen ab, die beim Beginnen einer Anmeldung verwendet werden können.",
+       "apihelp-query+authmanagerinfo-example-login-merged": "Ruft die Anfragen ab, die beim Beginnen einer Anmeldung verwendet werden können, mit zusammengeführten Formularfeldern.",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "Testet, ob die Authentifizierung für die Aktion <kbd>foo</kbd> ausreichend ist.",
        "apihelp-query+backlinks-description": "Alle Seiten finden, die auf die angegebene Seite verlinken.",
        "apihelp-query+backlinks-param-title": "Zu suchender Titel. Darf nicht zusammen mit <var>$1pageid</var> benutzt werden.",
        "apihelp-query+backlinks-param-pageid": "Zu suchende Seiten-ID. Darf nicht zusammen mit <var>$1title</var> benutzt werden.",
        "apihelp-query+watchlist-paramvalue-prop-sizes": "Ergänzt die alten und neuen Längen der Seite.",
        "apihelp-query+watchlist-paramvalue-type-new": "Seitenerstellungen.",
        "apihelp-query+watchlist-paramvalue-type-log": "Logbucheinträge.",
+       "apihelp-query+watchlistraw-description": "Ruft alle Seiten der Beobachtungsliste des aktuellen Benutzers ab.",
        "apihelp-query+watchlistraw-param-prop": "Zusätzlich zurückzugebende Eigenschaften:",
+       "apihelp-query+watchlistraw-param-fromtitle": "Titel (mit Namensraum-Präfix), bei dem die Aufzählung beginnen soll.",
+       "apihelp-query+watchlistraw-param-totitle": "Titel (mit Namensraum-Präfix), bei dem die Aufzählung enden soll.",
        "apihelp-resetpassword-param-user": "Benutzer, der zurückgesetzt werden soll.",
+       "apihelp-revisiondelete-description": "Löscht und stellt Versionen wieder her.",
+       "apihelp-revisiondelete-param-hide": "Was für jede Version versteckt werden soll.",
+       "apihelp-revisiondelete-param-show": "Was für jede Version wieder eingeblendet werden soll.",
        "apihelp-rsd-description": "Ein RSD-Schema (Really Simple Discovery) exportieren.",
        "apihelp-rsd-example-simple": "Das RSD-Schema exportieren",
        "apihelp-setnotificationtimestamp-param-entirewatchlist": "An allen beobachteten Seiten arbeiten.",
        "apihelp-stashedit-param-sectiontitle": "Der Titel für einen neuen Abschnitt.",
        "apihelp-stashedit-param-text": "Seiteninhalt.",
        "apihelp-stashedit-param-stashedtexthash": "Stattdessen zu verwendende Prüfsumme des Seiteninhalts von einem vorherigen Speicher.",
+       "apihelp-stashedit-param-contentmodel": "Inhaltsmodell des neuen Inhalts.",
        "apihelp-stashedit-param-summary": "Änderungszusammenfassung.",
        "apihelp-tag-param-reason": "Grund für die Änderung.",
        "apihelp-tokens-param-type": "Abzufragende Tokentypen.",
index 974e0aa..40388f9 100644 (file)
        "apihelp-query+authmanagerinfo-description": "Retrieve information about the current authentication status.",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Test whether the user's current authentication status is sufficient for the specified security-sensitive operation.",
        "apihelp-query+authmanagerinfo-param-requestsfor": "Fetch information about the authentication requests needed for the specified authentication action.",
-       "apihelp-query+filerepoinfo-example-login": "Fetch the requests that may be used when beginning a login.",
-       "apihelp-query+filerepoinfo-example-login-merged": "Fetch the requests that may be used when beginning a login, with form fields merged.",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Test whether authentication is sufficient for action <kbd>foo</kbd>.",
+       "apihelp-query+authmanagerinfo-example-login": "Fetch the requests that may be used when beginning a login.",
+       "apihelp-query+authmanagerinfo-example-login-merged": "Fetch the requests that may be used when beginning a login, with form fields merged.",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "Test whether authentication is sufficient for action <kbd>foo</kbd>.",
 
        "apihelp-query+backlinks-description": "Find all pages that link to the given page.",
        "apihelp-query+backlinks-param-title": "Title to search. Cannot be used together with <var>$1pageid</var>.",
index 059a7c5..7baf811 100644 (file)
        "apihelp-query+allusers-param-limit": "Cuántos nombres de usuario se devolverán.",
        "apihelp-query+allusers-param-activeusers": "Solo listar usuarios activos en {{PLURAL:$1|el último día|los $1 últimos días}}.",
        "apihelp-query+allusers-example-Y": "Listar usuarios que empiecen por <kbd>Y</kbd>.",
-       "apihelp-query+filerepoinfo-example-login": "Captura de las solicitudes que puede ser utilizadas al comienzo de inicio de sesión.",
+       "apihelp-query+authmanagerinfo-example-login": "Captura de las solicitudes que puede ser utilizadas al comienzo de inicio de sesión.",
        "apihelp-query+backlinks-description": "Encuentra todas las páginas que enlazan a la página dada.",
        "apihelp-query+backlinks-param-pageid": "Identificador de página que buscar. No puede usarse junto con <var>$1title</var>",
        "apihelp-query+backlinks-param-filterredir": "Cómo filtrar redirecciones. Si se establece a <kbd>nonredirects</kbd> cuando está activo <var>$1redirect</var>, esto sólo se aplica al segundo nivel.",
index 91b169f..ef9d7d1 100644 (file)
@@ -26,7 +26,8 @@
                        "Verdy p",
                        "Yasten",
                        "Trial",
-                       "Pols12"
+                       "Pols12",
+                       "The RedBurn"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> Toutes les fonctionnalités affichées sur cette page devraient fonctionner, mais l’API est encore en cours de développement et peut changer à tout moment. Inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un en-tête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet en-tête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:API:Errors_and_warnings|API: Errors and warnings]].\n\n<strong>Test :</strong> Pour faciliter le test des requêtes de l’API, voyez [[Special:ApiSandbox]].",
        "apihelp-edit-param-sectiontitle": "Le titre pour une nouvelle section.",
        "apihelp-edit-param-text": "Contenu de la page.",
        "apihelp-edit-param-summary": "Modifier le résumé. Également le titre de la section quand $1section=new et $1sectiontitle n’est pas défini.",
-       "apihelp-edit-param-tags": "Modifier les balises à appliquer à la révision.",
+       "apihelp-edit-param-tags": "Modifier les balises à appliquer à la version.",
        "apihelp-edit-param-minor": "Modification mineure.",
        "apihelp-edit-param-notminor": "Modification non mineure.",
        "apihelp-edit-param-bot": "Marquer cette modification comme robot.",
        "apihelp-options-example-change": "Modifier les préférences <kbd>skin</kbd> et <kbd>hideminor</kbd>.",
        "apihelp-options-example-complex": "Réinitialiser toutes les préférences, puis définir <kbd>skin</kbd> et <kbd>nickname</kbd>.",
        "apihelp-paraminfo-description": "Obtenir des informations sur les modules de l’API.",
-       "apihelp-paraminfo-param-modules": "Liste des noms de module (valeurs des paramètres <var>action</var> et <var>format</var>, ou <kbd>main</kbd>). Peut spécifier des sous-modules avec un <kbd>+</kbd>, ou tous les sous-modules avec <kbd>+*</kbd>, ou tousles sous-modules récursivement avec <kbd>+**</kbd>.",
+       "apihelp-paraminfo-param-modules": "Liste des noms de module (valeurs des paramètres <var>action</var> et <var>format</var>, ou <kbd>main</kbd>). Peut spécifier des sous-modules avec un <kbd>+</kbd>, ou tous les sous-modules avec <kbd>+*</kbd>, ou tous les sous-modules récursivement avec <kbd>+**</kbd>.",
        "apihelp-paraminfo-param-helpformat": "Format des chaînes d’aide.",
        "apihelp-paraminfo-param-querymodules": "Liste des noms de module de requêtage (valeur des paramètres <var>prop</var>, <var>meta</var> ou <var>list</var>=). Utiliser <kbd>$1modules=query+foo</kbd> au lieu de <kbd>$1querymodules=foo</kbd>.",
        "apihelp-paraminfo-param-mainmodule": "Obtenir aussi des informations sur le module principal (niveau supérieur). Utiliser plutôt <kbd>$1modules=main</kbd>.",
        "apihelp-protect-param-expiry": "Horodatages d’expiration. Si un seul horodatage est fourni, il sera utilisé pour toutes les protections. Utiliser <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd> ou <kbd>never</kbd> pour une protection sans expiration.",
        "apihelp-protect-param-reason": "Motif de (dé)protection.",
        "apihelp-protect-param-tags": "Modifier les balises à appliquer à l’entrée dans le journal de protection.",
-       "apihelp-protect-param-cascade": "Activer la protection en cascade (c’est-à-dire protéger les modèles transclus et les images utilisées dans cette page). Ignoré si aucun des niveaux de protection fournis ne supporte la mise en cascade.",
+       "apihelp-protect-param-cascade": "Activer la protection en cascade (c’est-à-dire protéger les modèles transclus et les images utilisées dans cette page). Ignoré si aucun des niveaux de protection fournis ne prend en charge la mise en cascade.",
        "apihelp-protect-param-watch": "Si activé, ajouter la page (dé)protégée à la liste de suivi de l'utilisateur actuel.",
        "apihelp-protect-param-watchlist": "Ajouter ou supprimer sans condition la page de la liste de suivi de l'utilisateur actuel, utiliser les préférences ou ne pas modifier le suivi.",
        "apihelp-protect-example-protect": "Protéger une page",
        "apihelp-query+authmanagerinfo-description": "Récupérer les informations concernant l’état d’authentification actuel.",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Tester si l’état d’authentification actuel de l’utilisateur est suffisant pour l’opération spécifiée comme sensible du point de vue sécurité.",
        "apihelp-query+authmanagerinfo-param-requestsfor": "Récupérer les informations sur les requêtes d’authentification nécessaires pour l’action d’authentification spécifiée.",
-       "apihelp-query+filerepoinfo-example-login": "Récupérer les requêtes qui peuvent être utilisées en commençant une connexion.",
-       "apihelp-query+filerepoinfo-example-login-merged": "Récupérer les requêtes qui peuvent être utilisées au début de la connexion, avec les champs de formulaire intégrés.",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Tester si l’authentification est suffisante pour l’action <kbd>foo</kbd>.",
+       "apihelp-query+authmanagerinfo-example-login": "Récupérer les requêtes qui peuvent être utilisées en commençant une connexion.",
+       "apihelp-query+authmanagerinfo-example-login-merged": "Récupérer les requêtes qui peuvent être utilisées au début de la connexion, avec les champs de formulaire intégrés.",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "Tester si l’authentification est suffisante pour l’action <kbd>foo</kbd>.",
        "apihelp-query+backlinks-description": "Trouver toutes les pages qui ont un lien vers la page donnée.",
        "apihelp-query+backlinks-param-title": "Titre à rechercher. Impossible à utiliser avec <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "ID de la page à chercher. Impossible à utiliser avec <var>$1title</var>.",
        "apihelp-query+search-paramvalue-prop-sectiontitle": "Ajoute le titre de la section correspondante.",
        "apihelp-query+search-paramvalue-prop-categorysnippet": "Ajoute un extrait analysé de la catégorie correspondante.",
        "apihelp-query+search-paramvalue-prop-isfilematch": "Ajoute un booléen indiquant si la recherche correspond au contenu du fichier.",
-       "apihelp-query+search-paramvalue-prop-score": "<span class=\"apihelp-deprecated\">Obsolète et ignoré.</span>",
+       "apihelp-query+search-paramvalue-prop-score": "<span class=\"apihelp-deprecated\">Désuet et ignoré.</span>",
        "apihelp-query+search-paramvalue-prop-hasrelated": "<span class=\"apihelp-deprecated\">Obsolète et ignoré.</span>",
        "apihelp-query+search-param-limit": "Combien de pages renvoyer au total.",
        "apihelp-query+search-param-interwiki": "Inclure les résultats interwiki dans la recherche, s’ils sont disponibles.",
        "apihelp-query+siteinfo-paramvalue-prop-fileextensions": "Renvoie la liste des extensions de fichier (types de fichier) autorisées au téléchargement.",
        "apihelp-query+siteinfo-paramvalue-prop-rightsinfo": "Renvoie l’information sur les droits du wiki (sa licence), si elle est disponible.",
        "apihelp-query+siteinfo-paramvalue-prop-restrictions": "Renvoie l’information sur les types de restriction disponibles (protection).",
-       "apihelp-query+siteinfo-paramvalue-prop-languages": "Renvoie une liste des langues que supporte MédiaWiki (éventuellement localisé en utilisant <var>$1inlanguagecode</var>).",
+       "apihelp-query+siteinfo-paramvalue-prop-languages": "Renvoie une liste des langues que MédiaWiki prend en charge (éventuellement localisée en utilisant <var>$1inlanguagecode</var>).",
        "apihelp-query+siteinfo-paramvalue-prop-skins": "Renvoie une liste de tous les habillages activés (éventuellement localisé en utilisant <var>$1inlanguagecode</var>, sinon dans la langue du contenu).",
        "apihelp-query+siteinfo-paramvalue-prop-extensiontags": "Renvoie une liste des balises d’extension de l’analyseur.",
        "apihelp-query+siteinfo-paramvalue-prop-functionhooks": "Renvoie une liste des accroches de fonction de l’analyseur.",
        "api-pageset-param-generator": "Obtenir la liste des pages sur lesquelles travailler en exécutant le module de recherche spécifié.\n\n<strong>NOTE :<strong> les noms de paramètre du générateur doivent être préfixés avec un « g », voir les exemples.",
        "api-pageset-param-redirects-generator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>, et dans les pages renvoyées par <var>$1generator</var>.",
        "api-pageset-param-redirects-nogenerator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>.",
-       "api-pageset-param-converttitles": "Convertir les titres dans d’autres variantes si nécessaire. Fonctionne uniquement si la langue de contenu du wiki supporte la conversion en variantes. Les langues qui supportent la conversion en variante incluent $1.",
+       "api-pageset-param-converttitles": "Convertir les titres dans d’autres variantes si nécessaire. Fonctionne uniquement si la langue de contenu du wiki prend en charge la conversion en variantes. Les langues qui prennent en charge la conversion en variante incluent $1.",
        "api-help-title": "Aide de l’API de MediaWiki",
        "api-help-lead": "Ceci est une page d’aide de l’API de MediaWiki générée automatiquement.\n\nDocumentation et exemples : https://www.mediawiki.org/wiki/API",
        "api-help-main-header": "Module principal",
index bdb1bec..79e36cf 100644 (file)
        "apihelp-query+authmanagerinfo-description": "Recuperar información sobre o estado de autenticación actual.",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Comprobar se o estado de autenticación actual do usuario é abondo para a operación especificada como sensible dende o punto de vista da seguridade.",
        "apihelp-query+authmanagerinfo-param-requestsfor": "Recuperar a información sobre as peticións de autenticación necesarias para a acción de autenticación especificada.",
-       "apihelp-query+filerepoinfo-example-login": "Recuperar as peticións que poden ser usadas ó comezo dunha conexión.",
-       "apihelp-query+filerepoinfo-example-login-merged": "Recuperar as peticións que poden ser usadas ó comezo dunha conexión, xunto cos campos de formulario integrados.",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Probar se a autenticación é abondo para a acción <kbd>foo</kbd>.",
+       "apihelp-query+authmanagerinfo-example-login": "Recuperar as peticións que poden ser usadas ó comezo dunha conexión.",
+       "apihelp-query+authmanagerinfo-example-login-merged": "Recuperar as peticións que poden ser usadas ó comezo dunha conexión, xunto cos campos de formulario integrados.",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "Probar se a autenticación é abondo para a acción <kbd>foo</kbd>.",
        "apihelp-query+backlinks-description": "Atopar todas as páxinas que ligan coa páxina dada.",
        "apihelp-query+backlinks-param-title": "Título a buscar. Non pode usarse xunto con <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "Identificador de páxina a buscar. Non pode usarse xunto con <var>$1title</var>.",
index 670a8a5..dd0d07b 100644 (file)
        "apihelp-options-example-change": "לשנות את ההעדפות <kbd>skin</kbd> ו־<kbd>hideminor</kbd>.",
        "apihelp-options-example-complex": "לאתחל את כל ההעדפות ואז להגדיר את <kbd>skin</kbd> ואת <kbd>nickname</kbd>.",
        "apihelp-paraminfo-description": "קבלת מידע על יחידות של API.",
-       "apihelp-paraminfo-param-modules": "רשימה של שמות יחידות (ערכים של הפרמטרים <var>action</var> ו־<var>format</var>, או <kbd>main</kbd>). אפשר להגדיר תת־יחידות עם <kbd>+</kbd>.",
+       "apihelp-paraminfo-param-modules": "רשימה של שמות יחידות (ערכים של הפרמטרים <var>action</var> ו־<var>format</var>, או <kbd>main</kbd>). אפשר להגדיר תת־יחידות עם <kbd>+</kbd>, או כל התת־מודולים עם <kbd dir=\"ltr\">+*</kbd>, או כל התת־מודולים באופן רקורסיבי עם <kbd dir=\"ltr\">+**</kbd>.",
        "apihelp-paraminfo-param-helpformat": "תסדיר מחרוזות העזרה.",
        "apihelp-paraminfo-param-querymodules": "רשימת שמות יחידות query (ערך של הפרמטר <var>prop</var>‏, <var>meta</var> או <var>list</var>). יש להשתמש ב־<kbd>$1modules=query+foo</kbd> במקום <kbd>$1querymodules=foo</kbd>.",
        "apihelp-paraminfo-param-mainmodule": "קבלת מידע עם היחידה הראשית (העליונה). יש להשתמש ב־<kbd>$1modules=main</kbd> במקום זה.",
        "apihelp-paraminfo-param-pagesetmodule": "קבלת מידע גם על יחידת pageset (שמספק את titles=‎ וידידיו).",
        "apihelp-paraminfo-param-formatmodules": "רשימת שמות תסדירים (ערכים של הפרמטר <var>format</var>). יש להשתמש ב־<var>$1modules</var> במקום זה.",
        "apihelp-paraminfo-example-1": "הצגת מידע עבור <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>‏, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>‏, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>‏, ו־<kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+       "apihelp-paraminfo-example-2": "הצגת מידע עבור כל התת־מודולים של <kbd>[[Special:ApiHelp/query|action=query]]</kbd>.",
        "apihelp-parse-description": "מפענח את התוכן ומחזיר פלט מפענח.\n\nר' את יחידת ה־prop השיונות של <kbd>[[Special:ApiHelp/query|action=query]]</kbd> כדי לקבל מידע על הגרסה הנוכחית של הדף.\n\nיש מספר דרכים לציין טקסט לפענוח:\n# ציון דף או גרסה באמצעות <var>$1page</var>‏, <var>$1pageid</var>, או <var>$1oldid</var>.\n# ציון התוכן במפורש, באמצעות <var>$1text</var>‏, <var>$1title</var>, ו־<var>$1contentmodel</var>.\n# ציון רק של  התקציר לפענוח. ל־<var>$1prop</var> צריך לתת ערך ריק.",
        "apihelp-parse-param-title": "שם הדף שהטקסט שייך אליו. אם זה מושמט, יש לציין את <var>$1contentmodel</var>, ו־[[API]] ישמש ככותרת.",
        "apihelp-parse-param-text": "הטקסט לפענוח. יש להשתמש ב־<var>$1title</var> או ב־<var>$1contentmodel</var>.",
        "apihelp-query+authmanagerinfo-description": "אחזור מידע אודות מצב האימות הנוכחי.",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "בדיקה האם מצב האימות הנוכחי של המשתמש מספיק בשביל הפעולה הרגישה מבחינת אבטחה שצוינה.",
        "apihelp-query+authmanagerinfo-param-requestsfor": "אחזור מידע על בקשות האימות הדרושות לפעולת האימות המבוקשת.",
-       "apihelp-query+filerepoinfo-example-login": "אחזור הבקשות שיכולות לשמש לתחילת הכניסה.",
-       "apihelp-query+filerepoinfo-example-login-merged": "אחזור הבקשות שיכולות לשמש לתחילת הכניסה, עם שדות טופס ממוזגים.",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "בדיקה האם האימות מספיק בשביל הפעולה <kbd>foo</kbd>.",
+       "apihelp-query+authmanagerinfo-example-login": "אחזור הבקשות שיכולות לשמש לתחילת הכניסה.",
+       "apihelp-query+authmanagerinfo-example-login-merged": "אחזור הבקשות שיכולות לשמש לתחילת הכניסה, עם שדות טופס ממוזגים.",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "בדיקה האם האימות מספיק בשביל הפעולה <kbd>foo</kbd>.",
        "apihelp-query+backlinks-description": "מציאת כל הדפים שמקשרים לדף הנתון.",
        "apihelp-query+backlinks-param-title": "איזו כותרת לחפש. לא ניתן להשתמש בזה יחד עם <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "מזהה דף לחיפוש. לא ניתן להשתמש בזה יחד עם <var>$1title</var>.",
index d053593..9786543 100644 (file)
        "apihelp-query+authmanagerinfo-description": "Recupera informazioni circa l'attuale stato di autenticazione.",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Verifica se lo stato di autenticazione dell'utente attuale è sufficiente per la specifica operazione sensibile alla sicurezza.",
        "apihelp-query+authmanagerinfo-param-requestsfor": "Recupera informazioni circa le richieste di autenticazione necessarie per la specifica azione di autenticazione.",
-       "apihelp-query+filerepoinfo-example-login": "Recupera le richieste che possono essere utilizzate quando si inizia l'accesso.",
-       "apihelp-query+filerepoinfo-example-login-merged": "Recupera le richieste che possono essere utilizzate quando si inizia l'accesso, con i campi del modulo uniti.",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Verificare se l'autenticazione è sufficiente per l'azione <kbd>foo</kbd>.",
+       "apihelp-query+authmanagerinfo-example-login": "Recupera le richieste che possono essere utilizzate quando si inizia l'accesso.",
+       "apihelp-query+authmanagerinfo-example-login-merged": "Recupera le richieste che possono essere utilizzate quando si inizia l'accesso, con i campi del modulo uniti.",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "Verificare se l'autenticazione è sufficiente per l'azione <kbd>foo</kbd>.",
        "apihelp-query+backlinks-description": "Trova tutte le pagine che puntano a quella specificata.",
        "apihelp-query+backlinks-param-namespace": "Il namespace da elencare.",
        "apihelp-query+backlinks-param-dir": "La direzione in cui elencare.",
diff --git a/includes/api/i18n/lij.json b/includes/api/i18n/lij.json
new file mode 100644 (file)
index 0000000..5acd38a
--- /dev/null
@@ -0,0 +1,103 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Giromin Cangiaxo"
+               ]
+       },
+       "apihelp-main-param-action": "Açion da compî.",
+       "apihelp-main-param-format": "Formato de l'output.",
+       "apihelp-main-param-assert": "Veifica che l'utente o l'agge effetoòu l'accesso se s'è impostou <kbd>user</kbd>, ò ch'o l'agge i permissi di bot se s'è impostou <kbd>bot</kbd>.",
+       "apihelp-main-param-requestid": "Tutti i valoî fornii saian incruxi inta risposta. Porieivan ese doeuviæ pe distingue e receste.",
+       "apihelp-main-param-servedby": "Inciodi into risultou o nomme de l'host ch'o l'ha servio a recesta.",
+       "apihelp-main-param-curtimestamp": "Inciodi into risultou o timestamp attoâ.",
+       "apihelp-block-description": "Blocca un utente.",
+       "apihelp-block-param-user": "Nomme utente, adresso IP o range di IP da bloccâ.",
+       "apihelp-block-param-expiry": "Tempo de scadença. O poeu ese relativo (presempio, <kbd>5 months</kbd> o <kbd>2 weeks</kbd>) ò assoluo (presempio <kbd>2014-09-18T12:34:56Z</kbd>). Se impostou a <kbd>infinite</kbd>, <kbd>indefinite</kbd> ò <kbd>never</kbd>, o blòcco o no descaziâ mai.",
+       "apihelp-block-param-reason": "Raxon do blòcco.",
+       "apihelp-block-param-anononly": "Blocca solo che i utenti non registræ (saiv'a dî disattiva i contributi anonnimi da questo adresso IP).",
+       "apihelp-block-param-nocreate": "Impedisci a creaçion de utençe.",
+       "apihelp-block-param-autoblock": "Blocca aotomaticamente l'urtimo adreçço IP doeuviou da l'utente e i succescivi co-i quæ tentan l'accesso",
+       "apihelp-block-param-hidename": "Ascondi o nomme utente da-o registro di blocchi (Ghe voeu i permissi de <code>hideuser</code>).",
+       "apihelp-block-param-reblock": "Se l'utente o l'è za bloccou, sorvescrive o blocco existente.",
+       "apihelp-block-param-watchuser": "Oserva a paggina utente e e paggine de discuscion utente de l'utente ò de l'adresso IP.",
+       "apihelp-block-example-ip-simple": "Blocca l'adresso IP <kbd>192.0.2.5</kbd> pe trei giorni con motivaçion <kbd>First strike</kbd>.",
+       "apihelp-block-example-user-complex": "Blocca l'utente <kbd>Vandal</kbd> a tempo indeterminou con motivaçion <kbd>Vandalism</kbd>, e impediscighe a creaçion de noeuve utençe e l'invio de e-mail.",
+       "apihelp-changeauthenticationdata-description": "Modificâ i dæti d'aotenticaçion pe l'utente corente.",
+       "apihelp-changeauthenticationdata-example-password": "Tentativo de modificâ a password de l'utente corente a <kbd>ExamplePassword</kbd>.",
+       "apihelp-checktoken-description": "Veifica a validitæ de 'n token da <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
+       "apihelp-checktoken-param-type": "Tipo de token in corso de test.",
+       "apihelp-checktoken-param-token": "Token da testâ.",
+       "apihelp-checktoken-param-maxtokenage": "Mascima etæ consentia pe-o token, in segondi.",
+       "apihelp-checktoken-example-simple": "Veifica a validitæ de 'n token <kbd>csrf</kbd>.",
+       "apihelp-clearhasmsg-description": "Scassa o flag <code>hasmsg</code> pe l'utente corente.",
+       "apihelp-clearhasmsg-example-1": "Scassa o flag <code>hasmsg</code> pe l'utente corente.",
+       "apihelp-clientlogin-description": "Accedi a-o wiki doeuviando o flusso interattivo.",
+       "apihelp-clientlogin-example-login": "Avvia o processo d'accesso a-a wiki comme utente <kbd>Example</kbd> con password <kbd>ExamplePassword</kbd>.",
+       "apihelp-clientlogin-example-login2": "Continnoa l'accesso doppo una risposta de l'<samp>UI</samp> pe l'aotenticaçion a doî fattoî, fornindo un <var>OATHToken</var> de <kbd>987654</kbd>.",
+       "apihelp-compare-description": "Otegni e differençe tra 2 paggine.\n\nUn nummero de revixon, o tittolo de 'na paggina, ò un ID de paggina o dev'ese indicou segge pe-o \"da\" che pe-o \"a\".",
+       "apihelp-compare-param-fromtitle": "Primmo tittolo da confrontâ.",
+       "apihelp-compare-param-fromid": "Primo ID de paggina da confrontâ.",
+       "apihelp-compare-param-fromrev": "Primma revixon da confrontâ.",
+       "apihelp-compare-param-totitle": "Segondo tittolo da confrontâ.",
+       "apihelp-compare-param-toid": "Segondo ID de paggina da confrontâ.",
+       "apihelp-compare-param-torev": "Segonda revixon da confrontâ.",
+       "apihelp-compare-example-1": "Crea un diff tra revixon 1 e revixon 2.",
+       "apihelp-createaccount-description": "Crea una noeuva utença.",
+       "apihelp-createaccount-param-preservestate": "Se <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> o l'ha restituto true pe <samp>hasprimarypreservedstate</samp>, e receste contrssegnæ comme <samp>primary-required</samp> dovieivan ese omisse. Se invece o l'ha restituio un valô non voeuo pe <samp>preservedusername</samp>, quello nomme utente o dev'ese doeuviou pe-o parammetro <var>username</var>.",
+       "apihelp-createaccount-example-create": "Avvia o processo de creaçion d'utente <kbd>Example</kbd> con password <kbd>ExamplePassword</kbd>.",
+       "apihelp-createaccount-param-name": "Nomme utente",
+       "apihelp-createaccount-param-password": "Password (a saiâ ignorâ se l'è impostou <var>$1mailpassword</var>).",
+       "apihelp-createaccount-param-domain": "Dominnio pe l'aotenticaçion esterna (opçionâ).",
+       "apihelp-createaccount-param-email": "Adresso Email de l'utente (opçionâ).",
+       "apihelp-createaccount-param-realname": "Nomme veo de l'utente (opçionâ).",
+       "apihelp-createaccount-param-mailpassword": "Se impostou insce 'n qualonque valô, una password random (caxoâ) a saiâ inviâ a l'utente.",
+       "apihelp-createaccount-param-reason": "Raxon facortativa da creaçion de l'utença da insei inti registri.",
+       "apihelp-createaccount-param-language": "Codiçe de lengua da impostâ comme predefinia pe l'utente (opçionâ, pe difetto a l'è a lengua do contegnuo).",
+       "apihelp-createaccount-example-pass": "Crea l'utente <kbd>testuser</kbd> con password <kbd>test123</kbd>.",
+       "apihelp-createaccount-example-mail": "Crea l'utente <kbd>testmailuser</kbd> e mandighe via e-mail una password generâ abrettio.",
+       "apihelp-delete-description": "Scassa 'na paggina",
+       "apihelp-delete-param-title": "Tittolo da paggina che se dexîa eliminâ. O no poeu vese doeuviou insemme a <var>$1pageid</var>.",
+       "apihelp-delete-param-pageid": "ID de paggina da paggina da scassâ. O no poeu vese doeuviou insemme con <var>$1title</var>.",
+       "apihelp-delete-param-reason": "Raxon da scassatua. S'a no saiâ indicâ, saiâ doeuviou 'na raxon generâ aotomaticamente.",
+       "apihelp-delete-param-watch": "O l'azonze a paggina a-a lista di oservæ speciali de l'utente corente.",
+       "apihelp-delete-param-unwatch": "O rimoeuve a pagina da-a lista di oservæ speciali de l'utente corente.",
+       "apihelp-delete-param-oldimage": "O nomme da vegia inmaggine da scassâ, comme fornia da [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]].",
+       "apihelp-delete-example-simple": "Scassa <kbd>Main Page</kbd>.",
+       "apihelp-delete-example-reason": "Scassa a <kbd>Main Page</kbd> con motivaçion <kbd>Preparing for move</kbd>.",
+       "apihelp-disabled-description": "Questo modulo o l'è stæto disabilitou.",
+       "apihelp-edit-description": "Crea e modifica paggine.",
+       "apihelp-edit-param-title": "Tittolo da paggina da modificâ. O no poeu vese doeuviou insemme a <var>$1pageid</var>.",
+       "apihelp-edit-param-pageid": "ID de paggina da paggina da modificâ. O no poeu ese doeuviou insemme a <var>$1title</var>.",
+       "apihelp-edit-param-section": "Nummero de seçion. <kbd>0</kbd> pe-a seçion de d'ato, <kbd>new</kbd> pe 'na noeuva seçion.",
+       "apihelp-edit-param-sectiontitle": "O tittolo pe 'na noeuva seçion.",
+       "apihelp-edit-param-text": "Contegnuo da paggina.",
+       "apihelp-edit-param-summary": "Ogetto da modiffica. E ascì tittolo da seçion se $1sezione=new e $1sectiontitle o no l'è impostou.",
+       "apihelp-edit-param-tags": "Cangia i tag da apricâ a-a revixon.",
+       "apihelp-edit-param-minor": "Cangiamento menô.",
+       "apihelp-edit-param-notminor": "Cangiamento non-menô.",
+       "apihelp-edit-param-bot": "Marca sta modiffica comme bot.",
+       "apihelp-edit-param-createonly": "No modificâ a paggina s'a l'existe za.",
+       "apihelp-edit-param-nocreate": "O genera un errô se a paggina a no l'existe.",
+       "apihelp-edit-param-watch": "O l'azonze a paggina a-a lista di oservæ speciali de l'utente corente.",
+       "apihelp-edit-param-unwatch": "O rimoeuve a pagina da-a lista di oservæ speciali de l'utente corente.",
+       "apihelp-edit-param-redirect": "Resciorvi aotomaticamente i rimandi.",
+       "apihelp-edit-param-contentmodel": "Modello de contegnuo di noeuvi contegnui.",
+       "apihelp-edit-param-token": "O token o dev'ese delongo inviou comme urtimo parammetro, ò aomeno doppo o parametro $1text.",
+       "apihelp-edit-example-edit": "Modiffica 'na paggina.",
+       "apihelp-edit-example-prepend": "Antepon-i <kbd>_&#95;NOTOC_&#95;</kbd> a 'na paggina.",
+       "apihelp-emailuser-description": "Manda 'n'e-mail a 'n utente.",
+       "apihelp-emailuser-param-target": "Utente a chi inviâ l'e-mail.",
+       "apihelp-emailuser-param-subject": "Ogetto de l'e-mail.",
+       "apihelp-emailuser-param-text": "Testo de l'e-mail.",
+       "apihelp-emailuser-param-ccme": "Mandime una copia de questa mail.",
+       "apihelp-emailuser-example-email": "Manda un'e-mail a l'utente <kbd>WikiSysop</kbd> co-o testo <kbd>Content</kbd>.",
+       "apihelp-expandtemplates-description": "Espandi tutti i template into wikitesto.",
+       "apihelp-expandtemplates-param-title": "Tittolo da paggina.",
+       "apihelp-expandtemplates-param-text": "Wikitesto da convertî.",
+       "apihelp-expandtemplates-param-prop": "Quæ informaçion otegnî.\n\nNotta che se no l'è seleçionou arcun valô, o risultou o contegniâ o codiçe wiki, ma l'output o saiâ inte 'n formato obsoleto.",
+       "apihelp-expandtemplates-paramvalue-prop-wikitext": "O wikitext espanso.",
+       "apihelp-expandtemplates-paramvalue-prop-properties": "Propietæ da paggina definie da-e paole magiche esteise into wikitesto.",
+       "apihelp-expandtemplates-paramvalue-prop-volatile": "Se l'output o segge volatile e o no 'agge da ese riadoeuviou atr'onde a l'interno da paggina.",
+       "apihelp-expandtemplates-paramvalue-prop-ttl": "O tempo mascimo doppo o quæ e memoizaçioin tempoannie (cache) do risultou dovieivan ese invalidæ.",
+       "apihelp-feedcontributions-param-feedformat": "O formato do feed."
+}
index abbc69b..caa89b5 100644 (file)
        "apihelp-query+authmanagerinfo-description": "{{doc-apihelp-description|query+authmanagerinfo}}",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "{{doc-apihelp-param|query+authmanagerinfo|securitysensitiveoperation}}",
        "apihelp-query+authmanagerinfo-param-requestsfor": "{{doc-apihelp-param|query+authmanagerinfo|requestsfor}}",
-       "apihelp-query+filerepoinfo-example-login": "{{doc-apihelp-example|query+filerepoinfo}}",
-       "apihelp-query+filerepoinfo-example-login-merged": "{{doc-apihelp-example|query+filerepoinfo}}",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "{{doc-apihelp-example|query+filerepoinfo}}",
+       "apihelp-query+authmanagerinfo-example-login": "{{doc-apihelp-example|query+authmanagerinfo}}",
+       "apihelp-query+authmanagerinfo-example-login-merged": "{{doc-apihelp-example|query+authmanagerinfo}}",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "{{doc-apihelp-example|query+authmanagerinfo}}",
        "apihelp-query+backlinks-description": "{{doc-apihelp-description|query+backlinks}}",
        "apihelp-query+backlinks-param-title": "{{doc-apihelp-param|query+backlinks|title}}",
        "apihelp-query+backlinks-param-pageid": "{{doc-apihelp-param|query+backlinks|pageid}}",
index af95729..e1cb982 100644 (file)
        "api-help-param-type-boolean": "Тип: двоичный ([[Special:ApiHelp/main#main/datatypes|details]])",
        "api-help-param-type-timestamp": "Тип: {{PLURAL:$1|1=timestamp|2=list of timestamps}} ([[Special:ApiHelp/main#main/datatypes|allowed formats]])",
        "api-help-param-type-user": "Тип: {{PLURAL:$1|1=user name|2=list of user names}}",
-       "api-help-param-list": "{{PLURAL:$1|1=Одно из следующих значений|2=Значения (разделённые <kbd>{{!}}</kbd>)}}: $2",
+       "api-help-param-list": "{{PLURAL:$1|1=Одно из следующих значений|2=Значения (разделённые с помощью <kbd>{{!}}</kbd> или [[Special:ApiHelp/main#main/datatypes|альтернативного варианта]])}}: $2",
        "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Должен быть пустым|может быть пустым, или $2}}",
        "api-help-param-limit": "Не более чем $1 разрешено.",
        "api-help-param-limit2": "Разрешено не более чем $1 ($2 для ботов).",
        "api-help-param-integer-min": "{{PLURAL:$1|1=value|2=values}} должен быть не меньше чем $2.",
        "api-help-param-integer-max": "{{PLURAL:$1|1=value|2=values}} должен быть не больше чем $3.",
        "api-help-param-integer-minmax": "{{PLURAL:$1|1=value|2=values}} должен быть между $2 и $3.",
-       "api-help-param-multi-separate": "Разделяйте значения с помощью <kbd>|</kbd>.",
+       "api-help-param-multi-separate": "Разделяйте значения с помощью <kbd>|</kbd> или [[Special:ApiHelp/main#main/datatypes|альтернативного варианта]].",
        "api-help-param-multi-max": "Максимальное количество значений должно быть {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} для ботов).",
        "api-help-param-default": "По умолчанию: $1",
        "api-help-param-default-empty": "По умолчанию: <span class=\"apihelp-empty\">(пусто)</span>",
index 2f65c8e..1a079df 100644 (file)
        "apihelp-query+authmanagerinfo-description": "Отримати інформацію про поточний стан автентифікації.",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Перевірити, чи поточний стан автентифікації користувача є достатнім для даної конфіденційної операції.",
        "apihelp-query+authmanagerinfo-param-requestsfor": "Отримати інформацію про запити автентифікації, потрібні для даної дії автентифікації.",
-       "apihelp-query+filerepoinfo-example-login": "Вибірка запитів, що можуть бути використані при початку входу.",
-       "apihelp-query+filerepoinfo-example-login-merged": "Отримати запити, які можуть бути використані при початку входу, з об'єднаними полями форми.",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Перевірити чи автентифікація є достатньою для дії <kbd>foo</kbd>.",
+       "apihelp-query+authmanagerinfo-example-login": "Вибірка запитів, що можуть бути використані при початку входу.",
+       "apihelp-query+authmanagerinfo-example-login-merged": "Отримати запити, які можуть бути використані при початку входу, з об'єднаними полями форми.",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "Перевірити чи автентифікація є достатньою для дії <kbd>foo</kbd>.",
        "apihelp-query+backlinks-description": "Знайти усі сторінки, що посилаються на подану сторінку.",
        "apihelp-query+backlinks-param-title": "Назва для пошуку. Не можна використати разом з <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "ID сторінки для пошуку. Не можна використати разом з <var>$1title</var>.",
        "api-help-param-deprecated": "Застарілий.",
        "api-help-param-required": "Цей параметр є обов'язковим.",
        "api-help-datatypes-header": "Типи даних",
-       "api-help-datatypes": "Ð\94еÑ\8fкÑ\96 Ñ\82ипи Ð¿Ð°Ñ\80амеÑ\82Ñ\80Ñ\96в Ñ\83 Ð·Ð°Ð¿Ð¸Ñ\82аÑ\85 API Ð¿Ð¾Ñ\82Ñ\80ебÑ\83Ñ\8eÑ\82Ñ\8c Ñ\88иÑ\80Ñ\88ого Ð¿Ð¾Ñ\8fÑ\81неннÑ\8f:\n;boolean\n:Ð\9bогÑ\96Ñ\87нÑ\96 Ð¿Ð°Ñ\80амеÑ\82Ñ\80и Ð¿Ñ\80аÑ\86Ñ\8eÑ\8eÑ\82Ñ\8c Ñ\8fк Ð³Ð°Ð»Ð¾Ñ\87ки HTML: Ñ\8fкÑ\89о Ð¿Ð°Ñ\80амеÑ\82Ñ\80 Ð²ÐºÐ°Ð·Ð°Ð½Ð¾, Ð½Ðµ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾ Ð²Ñ\96д Ð·Ð½Ð°Ñ\87еннÑ\8f, Ð²Ñ\96н Ð²Ð²Ð°Ð¶Ð°Ñ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\96Ñ\81Ñ\82инним. Ð©Ð¾Ð± Ð·Ð½Ð°Ñ\87еннÑ\8f Ð±Ñ\83ло Ñ\85ибним, Ð¿Ñ\80опÑ\83Ñ\81Ñ\82Ñ\96Ñ\82Ñ\8c Ð¿Ð°Ñ\80амеÑ\82Ñ\80 Ð·Ð¾Ð²Ñ\81Ñ\96м.\n;timestamp\n:ЧаÑ\81овÑ\96 Ð¼Ñ\96Ñ\82ки Ð¼Ð¾Ð¶Ñ\83Ñ\82Ñ\8c Ð±Ñ\83Ñ\82и Ð²ÐºÐ°Ð·Ð°Ð½Ñ\96 Ñ\83 ÐºÑ\96лÑ\8cкоÑ\85 Ñ\84оÑ\80маÑ\82аÑ\85. Ð ÐµÐºÐ¾Ð¼ÐµÐ½Ð´Ñ\83Ñ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\87аÑ\81 Ñ\96 Ð´Ð°Ñ\82а Ð² ISO 8601. Ð£Ñ\81Ñ\96 Ð·Ð½Ð°Ñ\87еннÑ\8f Ñ\87аÑ\81Ñ\83 Ð² UTC, Ð±Ñ\83дÑ\8c\8fкÑ\96 Ñ\87аÑ\81овÑ\96 Ð¿Ð¾Ñ\8fÑ\81и Ñ\96гноÑ\80Ñ\83Ñ\8eÑ\82Ñ\8cÑ\81Ñ\8f.\n:* Ð\94аÑ\82а Ñ\96 Ñ\87аÑ\81 ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (пÑ\83нкÑ\82Ñ\83аÑ\86Ñ\96Ñ\8f Ñ\96 <kbd>Z</kbd> Ð½ÐµÐ¾Ð±Ð¾Ð²'Ñ\8fзоквÑ\96)\n:* Ð\94аÑ\82а Ñ\96 Ñ\87аÑ\81 ISO 8601 Ð· (Ñ\96гноÑ\80ованими) Ñ\87аÑ\81Ñ\82ками Ñ\81екÑ\83нди, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (деÑ\84Ñ\96Ñ\81и, Ð´Ð²Ð¾ÐºÑ\80апки Ñ\82а <kbd>Z</kbd> Ð½ÐµÐ¾Ð±Ð¾Ð²'Ñ\8fзковÑ\96)\n:* Ð¤Ð¾Ñ\80маÑ\82 MediaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Ð\97агалÑ\8cний Ñ\87иÑ\81ловий Ñ\84оÑ\80маÑ\82, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (необов'Ñ\8fзковий Ñ\87аÑ\81овий Ð¿Ð¾Ñ\8fÑ\81 <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd> Ð°Ð±Ð¾ <kbd>-<var>##</var></kbd> Ñ\96гноÑ\80Ñ\83Ñ\94Ñ\82Ñ\8cÑ\81Ñ\8f)\n:* Ð¤Ð¾Ñ\80маÑ\82 EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Ð¤Ð¾Ñ\80маÑ\82 RFC 2822 (Ñ\87аÑ\81овий Ð¿Ð¾Ñ\8fÑ\81 Ð¼Ð¾Ð¶Ðµ Ð±Ñ\83Ñ\82и Ð¾Ð¿Ñ\83Ñ\89ений), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Ð¤Ð¾Ñ\80маÑ\82 RFC 850 (Ñ\87аÑ\81овий Ð¿Ð¾Ñ\8fÑ\81 Ð¼Ð¾Ð¶Ðµ Ð±Ñ\83Ñ\82и Ð¾Ð¿Ñ\83Ñ\89ений), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Ð¤Ð¾Ñ\80маÑ\82 C ctime, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Ð¡ÐµÐºÑ\83нди Ð²Ñ\96д 1970-01-01T00:00:00Z Ñ\83 Ð²Ð¸Ð³Ð»Ñ\8fдÑ\96 Ñ\86Ñ\96лого Ñ\87иÑ\81ла Ð²Ñ\96д 1 Ð´Ð¾ 13 Ñ\86иÑ\84Ñ\80 (без <kbd>0</kbd>)\n:* Ð Ñ\8fдок <kbd>now</kbd>",
+       "api-help-datatypes": "Ð\92Ñ\85Ñ\96днÑ\96 Ð´Ð°Ð½Ñ\96 Ñ\83 MediaWiki Ð¼Ð°Ñ\8eÑ\82Ñ\8c Ð±Ñ\83Ñ\82и Ð² NFC-ноÑ\80малÑ\96зованомÑ\83 UTF-8. MediaWiki Ð¼Ð¾Ð¶Ðµ Ñ\81пÑ\80обÑ\83ваÑ\82и ÐºÐ¾Ð½Ð²ÐµÑ\80Ñ\82Ñ\83ваÑ\82и Ð²Ñ\85Ñ\96днÑ\96 Ð´Ð°Ð½Ñ\96 Ñ\96нÑ\88ого Ð²Ð¸Ð³Ð»Ñ\8fдÑ\83, Ð°Ð»Ðµ Ð²Ñ\96д Ñ\86Ñ\8cого Ð¼Ð¾Ð¶Ñ\83Ñ\82Ñ\8c Ð¿Ð¾Ñ\81Ñ\82Ñ\80аждаÑ\82и Ð´ÐµÑ\8fкÑ\96 Ð¾Ð¿ÐµÑ\80аÑ\86Ñ\96Ñ\97 (Ñ\8fк [[Special:ApiHelp/edit|Ñ\80едагÑ\83ваннÑ\8f]] Ð· Ð¿ÐµÑ\80евÑ\96Ñ\80коÑ\8e MD5).\n\nÐ\94еÑ\8fкÑ\96 Ñ\82ипи Ð¿Ð°Ñ\80амеÑ\82Ñ\80Ñ\96в Ñ\83 Ð·Ð°Ð¿Ð¸Ñ\82аÑ\85 API Ð¿Ð¾Ñ\82Ñ\80ебÑ\83Ñ\8eÑ\82Ñ\8c Ñ\88иÑ\80Ñ\88ого Ð¿Ð¾Ñ\8fÑ\81неннÑ\8f:\n;boolean\n:Ð\9bогÑ\96Ñ\87нÑ\96 Ð¿Ð°Ñ\80амеÑ\82Ñ\80и Ð¿Ñ\80аÑ\86Ñ\8eÑ\8eÑ\82Ñ\8c Ñ\8fк Ð³Ð°Ð»Ð¾Ñ\87ки HTML: Ñ\8fкÑ\89о Ð¿Ð°Ñ\80амеÑ\82Ñ\80 Ð²ÐºÐ°Ð·Ð°Ð½Ð¾, Ð½Ðµ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¾ Ð²Ñ\96д Ð·Ð½Ð°Ñ\87еннÑ\8f, Ð²Ñ\96н Ð²Ð²Ð°Ð¶Ð°Ñ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\96Ñ\81Ñ\82инним. Ð©Ð¾Ð± Ð·Ð½Ð°Ñ\87еннÑ\8f Ð±Ñ\83ло Ñ\85ибним, Ð¿Ñ\80опÑ\83Ñ\81Ñ\82Ñ\96Ñ\82Ñ\8c Ð¿Ð°Ñ\80амеÑ\82Ñ\80 Ð·Ð¾Ð²Ñ\81Ñ\96м.\n;timestamp\n:ЧаÑ\81овÑ\96 Ð¼Ñ\96Ñ\82ки Ð¼Ð¾Ð¶Ñ\83Ñ\82Ñ\8c Ð±Ñ\83Ñ\82и Ð²ÐºÐ°Ð·Ð°Ð½Ñ\96 Ñ\83 ÐºÑ\96лÑ\8cкоÑ\85 Ñ\84оÑ\80маÑ\82аÑ\85. Ð ÐµÐºÐ¾Ð¼ÐµÐ½Ð´Ñ\83Ñ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\87аÑ\81 Ñ\96 Ð´Ð°Ñ\82а Ð² ISO 8601. Ð£Ñ\81Ñ\96 Ð·Ð½Ð°Ñ\87еннÑ\8f Ñ\87аÑ\81Ñ\83 Ð² UTC, Ð±Ñ\83дÑ\8c\8fкÑ\96 Ñ\87аÑ\81овÑ\96 Ð¿Ð¾Ñ\8fÑ\81и Ñ\96гноÑ\80Ñ\83Ñ\8eÑ\82Ñ\8cÑ\81Ñ\8f.\n:* Ð\94аÑ\82а Ñ\96 Ñ\87аÑ\81 ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (пÑ\83нкÑ\82Ñ\83аÑ\86Ñ\96Ñ\8f Ñ\96 <kbd>Z</kbd> Ð½ÐµÐ¾Ð±Ð¾Ð²'Ñ\8fзоквÑ\96)\n:* Ð\94аÑ\82а Ñ\96 Ñ\87аÑ\81 ISO 8601 Ð· (Ñ\96гноÑ\80ованими) Ñ\87аÑ\81Ñ\82ками Ñ\81екÑ\83нди, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (деÑ\84Ñ\96Ñ\81и, Ð´Ð²Ð¾ÐºÑ\80апки Ñ\82а <kbd>Z</kbd> Ð½ÐµÐ¾Ð±Ð¾Ð²'Ñ\8fзковÑ\96)\n:* Ð¤Ð¾Ñ\80маÑ\82 MediaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Ð\97агалÑ\8cний Ñ\87иÑ\81ловий Ñ\84оÑ\80маÑ\82, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (необов'Ñ\8fзковий Ñ\87аÑ\81овий Ð¿Ð¾Ñ\8fÑ\81 <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd> Ð°Ð±Ð¾ <kbd>-<var>##</var></kbd> Ñ\96гноÑ\80Ñ\83Ñ\94Ñ\82Ñ\8cÑ\81Ñ\8f)\n:* Ð¤Ð¾Ñ\80маÑ\82 EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Ð¤Ð¾Ñ\80маÑ\82 RFC 2822 (Ñ\87аÑ\81овий Ð¿Ð¾Ñ\8fÑ\81 Ð¼Ð¾Ð¶Ðµ Ð±Ñ\83Ñ\82и Ð¾Ð¿Ñ\83Ñ\89ений), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Ð¤Ð¾Ñ\80маÑ\82 RFC 850 (Ñ\87аÑ\81овий Ð¿Ð¾Ñ\8fÑ\81 Ð¼Ð¾Ð¶Ðµ Ð±Ñ\83Ñ\82и Ð¾Ð¿Ñ\83Ñ\89ений), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Ð¤Ð¾Ñ\80маÑ\82 C ctime, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Ð¡ÐµÐºÑ\83нди Ð²Ñ\96д 1970-01-01T00:00:00Z Ñ\83 Ð²Ð¸Ð³Ð»Ñ\8fдÑ\96 Ñ\86Ñ\96лого Ñ\87иÑ\81ла Ð²Ñ\96д 1 Ð´Ð¾ 13 Ñ\86иÑ\84Ñ\80 (без <kbd>0</kbd>)\n:* Ð Ñ\8fдок <kbd>now</kbd>\n;алÑ\8cÑ\82еÑ\80наÑ\82ивний Ñ\80оздÑ\96лÑ\8cник Ð±Ð°Ð³Ð°Ñ\82Ñ\8cоÑ\85 Ð·Ð½Ð°Ñ\87енÑ\8c\n:Ð\9fаÑ\80амеÑ\82Ñ\80и, Ñ\89о Ð¿Ñ\80иймаÑ\8eÑ\82Ñ\8c Ð±Ð°Ð³Ð°Ñ\82о Ð·Ð½Ð°Ñ\87енÑ\8c, Ð·Ð°Ð·Ð²Ð¸Ñ\87ай Ð¿Ð¾Ð´Ð°Ñ\8eÑ\82Ñ\8cÑ\81Ñ\8f Ð·Ñ\96 Ð·Ð½Ð°Ñ\87еннÑ\8fми, Ñ\80оздÑ\96леними Ð²ÐµÑ\80Ñ\82икалÑ\8cноÑ\8e Ñ\80иÑ\81коÑ\8e, Ð½Ð°Ð¿Ñ\80иклад, <kbd>param=value1|value2</kbd> Ð°Ð±Ð¾ <kbd>param=value1%7Cvalue2</kbd>. Ð¯ÐºÑ\89о Ð·Ð½Ð°Ñ\87еннÑ\8f Ð¿Ð¾Ð²Ð¸Ð½Ð½Ðµ Ð¼Ñ\96Ñ\81Ñ\82иÑ\82и Ð²ÐµÑ\80Ñ\82икалÑ\8cнÑ\83 Ñ\80иÑ\81кÑ\83, Ð²Ð¸ÐºÐ¾Ñ\80иÑ\81Ñ\82овÑ\83йÑ\82е Ñ\8fк Ñ\80оздÑ\96лÑ\8cник U+001F (Ñ\80оздÑ\96лÑ\8cник Ð¾Ð´Ð¸Ð½Ð¸Ñ\86Ñ\8c) ''Ñ\82а'' Ð¿Ð¾Ñ\81Ñ\82авÑ\82е U+001F Ð¿ÐµÑ\80ед Ð·Ð½Ð°Ñ\87еннÑ\8fм, Ð½Ð°Ð¿Ñ\80иклад, <kbd>param=%1Fvalue1%1Fvalue2</kbd>.",
        "api-help-param-type-limit": "Тип: ціле число або <kbd>max</kbd>",
        "api-help-param-type-integer": "Тип: {{PLURAL:$1|1=ціле число|2=список цілих чисел}}",
        "api-help-param-type-boolean": "Тип: логічний ([[Special:ApiHelp/main#main/datatypes|деталі]])",
        "api-help-param-type-timestamp": "Тип: {{PLURAL:$1|1=часова мітка|2=список часових міток}} ([[Special:ApiHelp/main#main/datatypes|дозволені формати]])",
        "api-help-param-type-user": "Тип: {{PLURAL:$1|1=ім'я користувача|2=список імен користувачів}}",
-       "api-help-param-list": "{{PLURAL:$1|1=Одне з наступних значень|2=Значення (розділені через <kbd>{{!}}</kbd>)}}: $2",
+       "api-help-param-list": "{{PLURAL:$1|1=Одне з наступних значень|2=Значення (розділені через <kbd>{{!}}</kbd> або [[Special:ApiHelp/main#main/datatypes|альтернативу]])}}: $2",
        "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Повинно бути пустим|Може бути пустим або $2}}",
        "api-help-param-limit": "Дозволено не більше $1.",
        "api-help-param-limit2": "Дозволено не більше $1 ($2 для ботів).",
        "api-help-param-integer-max": "{{PLURAL:$1|1=Значення має бути|2=Значення мають бути}} не більше $3.",
        "api-help-param-integer-minmax": "{{PLURAL:$1|1=Значення має бути|2=Значення мають бути}} між $2 і $3.",
        "api-help-param-upload": "Повинно бути надіслано у формі надсилання файлу використовуючи multipart/form-data.",
-       "api-help-param-multi-separate": "Розділіть значення з допомогою <kbd>|</kbd>.",
+       "api-help-param-multi-separate": "Розділіть значення з допомогою <kbd>|</kbd> або [[Special:ApiHelp/main#main/datatypes|альтернативу]].",
        "api-help-param-multi-max": "Максимальна кількість значень — {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} для ботів).",
        "api-help-param-default": "За замовчуванням: $1",
        "api-help-param-default-empty": "За замовчуванням: <span class=\"apihelp-empty\">(пусто)</span>",
index adda3c4..573748d 100644 (file)
        "apihelp-options-example-change": "更改<kbd>skin</kbd>和<kbd>hideminor</kbd>设置。",
        "apihelp-options-example-complex": "重置所有设置,然后设置<kbd>skin</kbd>和<kbd>nickname</kbd>。",
        "apihelp-paraminfo-description": "获得关于API模块的信息。",
-       "apihelp-paraminfo-param-modules": "模块名称(<var>action</var>和<var>format</var>参数值,或<kbd>main</kbd>)的列表。可通过<kbd>+</kbd>指定子模块。",
+       "apihelp-paraminfo-param-modules": "模块名称(<var>action</var>和<var>format</var>参数值,或<kbd>main</kbd>)的列表。可通过<kbd>+</kbd>指定子模块,或通过<kbd>+*</kbd>指定所有子模块,或通过<kbd>+**</kbd>指定所有递归子模块。",
        "apihelp-paraminfo-param-helpformat": "帮助字符串的格式。",
        "apihelp-paraminfo-param-querymodules": "查询模块名称(<var>prop</var>、<var>meta</var>或<var>list</var>参数值)的列表。使用<kbd>$1modules=query+foo</kbd>而不是<kbd>$1querymodules=foo</kbd>。",
        "apihelp-paraminfo-param-mainmodule": "获取有关主要(最高级)模块的信息。也可使用<kbd>$1modules=main</kbd>。",
        "apihelp-paraminfo-param-pagesetmodule": "获取有关页面设置模块(提供titles=和朋友)的信息。",
        "apihelp-paraminfo-param-formatmodules": "格式模块名称(<var>format</var>参数的值)的列表。也可使用<var>$1modules</var>。",
        "apihelp-paraminfo-example-1": "显示<kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>、<kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>、<kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>和<kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>的信息。",
+       "apihelp-paraminfo-example-2": "显示<kbd>[[Special:ApiHelp/query|action=query]]</kbd>的所有子模块的信息。",
        "apihelp-parse-description": "解析内容并返回解析器输出。\n\n参见<kbd>[[Special:ApiHelp/query|action=query]]</kbd>的各种prop-module以从页面的当前版本获得信息。\n\n这里有几种方法可以指定解析的文本:\n# 指定一个页面或修订,使用<var>$1page</var>、<var>$1pageid</var>或<var>$1oldid</var>。\n# 明确指定内容,使用<var>$1text</var>、<var>$1title</var>和<var>$1contentmodel</var>。\n# 只指定一段摘要解析。<var>$1prop</var>应提供一个空值。",
        "apihelp-parse-param-title": "文本属于的页面标题。如果省略,<var>$1contentmodel</var>就必须被指定,且[[API]]将作为标题使用。",
        "apihelp-parse-param-text": "要解析的文本。使用<var>$1title</var>或<var>$1contentmodel</var>以控制内容模型。",
        "apihelp-query+authmanagerinfo-description": "检索有关当前身份验证状态的信息。",
        "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "测试用户当前的身份验证状态是否足够用于指定的安全敏感操作。",
        "apihelp-query+authmanagerinfo-param-requestsfor": "取得指定身份验证操作所需的有关身份验证请求的信息。",
-       "apihelp-query+filerepoinfo-example-login": "检索当开始登录时可能使用的请求。",
-       "apihelp-query+filerepoinfo-example-login-merged": "检索当开始登录时可能使用的请求,并合并表单字段。",
-       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "测试身份验证对操作<kbd>foo</kbd>是否足够。",
+       "apihelp-query+authmanagerinfo-example-login": "检索当开始登录时可能使用的请求。",
+       "apihelp-query+authmanagerinfo-example-login-merged": "检索当开始登录时可能使用的请求,并合并表单字段。",
+       "apihelp-query+authmanagerinfo-example-securitysensitiveoperation": "测试身份验证对操作<kbd>foo</kbd>是否足够。",
        "apihelp-query+backlinks-description": "查找所有链接至指定页面的页面。",
        "apihelp-query+backlinks-param-title": "要搜索的标题。不能与<var>$1pageid</var>一起使用。",
        "apihelp-query+backlinks-param-pageid": "要搜索的页面ID。不能与<var>$1title</var>一起使用。",
        "apihelp-query+search-paramvalue-prop-sectionsnippet": "Adds a parsed snippet of the matching section title.",
        "apihelp-query+search-paramvalue-prop-sectiontitle": "Adds the title of the matching section.",
        "apihelp-query+search-paramvalue-prop-categorysnippet": "Adds a parsed snippet of the matching category.",
-       "apihelp-query+search-paramvalue-prop-isfilematch": "Adds a boolean indicating if the search matched file content.",
+       "apihelp-query+search-paramvalue-prop-isfilematch": "添加布尔值,表明搜索是否匹配文件内容。",
        "apihelp-query+search-paramvalue-prop-score": "<span class=\"apihelp-deprecated\">已弃用并已忽略。</span>",
        "apihelp-query+search-paramvalue-prop-hasrelated": "<span class=\"apihelp-deprecated\">已弃用并已忽略。</span>",
        "apihelp-query+search-param-limit": "返回的总计页面数。",
        "api-help-permissions-granted-to": "{{PLURAL:$1|授予}}:$2",
        "api-help-right-apihighlimits": "在API查询中使用更高的上限(慢查询:$1;快查询:$2)。慢查询的限制也适用于多值参数。",
        "api-help-open-in-apisandbox": "<small>[在沙盒中打开]</small>",
-       "api-help-authmanager-general-usage": "使用此模块的一般程序是:\n# 通过<kbd>amirequestsfor=$4</kbd>取得来自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的可用字段,和来自<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>的<kbd>$5</kbd>令牌。\n# 向用户显示字段,并获得其提交内容。\n# 发送至此模块,提供<var>$1returnurl</var>及任何相关字段。\n# Check the <samp>status</samp> in the response.\n#* If you received <samp>PASS</samp> or <samp>FAIL</samp>, you're done. The operation either succeeded or it didn't.\n#* If you received <samp>UI</samp>, present the new fields to the user and obtain their submission. Then post to this module with <var>$1continue</var> and the relevant fields set, and repeat step 4.\n#* If you received <samp>REDIRECT</samp>, direct the user to the <samp>redirecttarget</samp> and wait for the return to <var>$1returnurl</var>. Then post to this module with <var>$1continue</var> and any fields passed to the return URL, and repeat step 4.\n#* If you received <samp>RESTART</samp>, that means the authentication worked but we don't have a linked user account. You might treat this as <samp>UI</samp> or as <samp>FAIL</samp>.",
+       "api-help-authmanager-general-usage": "使用此模块的一般程序是:\n# 通过<kbd>amirequestsfor=$4</kbd>取得来自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的可用字段,和来自<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>的<kbd>$5</kbd>令牌。\n# 向用户显示字段,并获得其提交内容。\n# 发送至此模块,提供<var>$1returnurl</var>及任何相关字段。\n# 在响应中检查<samp>status</samp>。\n#* 如果您收到了<samp>PASS</samp>或<samp>FAIL</samp>,您已经完成。The operation either succeeded or it didn't.\n#* 如果您收到了<samp>UI</samp>,present the new fields to the user and obtain their submission. Then post to this module with <var>$1continue</var> and the relevant fields set, and repeat step 4.\n#* 如果您收到了<samp>REDIRECT</samp>,direct the user to the <samp>redirecttarget</samp> and wait for the return to <var>$1returnurl</var>. Then post to this module with <var>$1continue</var> and any fields passed to the return URL, and repeat step 4.\n#* 如果您收到了<samp>RESTART</samp>,that means the authentication worked but we don't have a linked user account. You might treat this as <samp>UI</samp> or as <samp>FAIL</samp>.",
        "api-help-authmanagerhelper-request": "使用此身份验证请求,通过返回自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的<samp>id</samp>与<kbd>amirequestsfor=$1</kbd>。",
        "api-help-authmanagerhelper-messageformat": "返回消息使用的格式。",
        "api-help-authmanagerhelper-mergerequestfields": "合并用于所有身份验证请求的字段信息至一个数组中。",
+       "api-help-authmanagerhelper-preservestate": "从之前失败的登录尝试中保持状态,如果可能。",
+       "api-help-authmanagerhelper-continue": "此请求是在早先的<samp>UI</samp>或<samp>REDIRECT</samp>响应之后的附加请求。必需此值或<var>$1returnurl</var>。",
        "api-help-authmanagerhelper-additional-params": "此模块允许额外参数,取决于可用的身份验证请求。使用<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>与<kbd>amirequestsfor=$1</kbd>(或之前来自此模块的相应,如果可以)以决定可用请求及其使用的字段。",
        "api-credits-header": "制作人员",
        "api-credits": "API 开发人员:\n* Yuri Astrakhan(创建者,2006年9月~2007年9月的开发组领导)\n* Roan Kattouw(2007年9月~2009年的开发组领导)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Brad Jorsch(2013年至今的开发组领导)\n\n请将您的评论、建议和问题发送至mediawiki-api@lists.wikimedia.org,或提交错误请求至https://phabricator.wikimedia.org/。"
index a5c86be..89a22f8 100644 (file)
@@ -1540,7 +1540,7 @@ class AuthManager implements LoggerAwareInterface {
 
                $username = $user->getName();
 
-               // Try the local user from the slave DB
+               // Try the local user from the replica DB
                $localId = User::idFromName( $username );
                $flags = User::READ_NORMAL;
 
@@ -1679,14 +1679,12 @@ class AuthManager implements LoggerAwareInterface {
                try {
                        $status = $user->addToDatabase();
                        if ( !$status->isOk() ) {
-                               // double-check for a race condition (T70012)
-                               $localId = User::idFromName( $username, User::READ_LATEST );
-                               if ( $localId ) {
+                               // Double-check for a race condition (T70012). We make use of the fact that when
+                               // addToDatabase fails due to the user already existing, the user object gets loaded.
+                               if ( $user->getId() ) {
                                        $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
                                                'username' => $username,
                                        ] );
-                                       $user->setId( $localId );
-                                       $user->loadFromId( User::READ_LATEST );
                                        if ( $login ) {
                                                $this->setSessionDataForUser( $user );
                                        }
index c338284..f5571c7 100644 (file)
@@ -126,7 +126,7 @@ class TemporaryPasswordPrimaryAuthenticationProvider
                        return AuthenticationResponse::newAbstain();
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $row = $dbr->selectRow(
                        'user',
                        [
@@ -165,7 +165,7 @@ class TemporaryPasswordPrimaryAuthenticationProvider
                        return false;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $row = $dbr->selectRow(
                        'user',
                        [ 'user_newpassword', 'user_newpass_time' ],
index 8c2a19e..9dfabfd 100644 (file)
@@ -137,13 +137,13 @@ class BacklinkCache {
        }
 
        /**
-        * Get the slave connection to the database
+        * Get the replica DB connection to the database
         * When non existing, will initialize the connection.
         * @return DatabaseBase
         */
        protected function getDB() {
                if ( !isset( $this->db ) ) {
-                       $this->db = wfGetDB( DB_SLAVE );
+                       $this->db = wfGetDB( DB_REPLICA );
                }
 
                return $this->db;
index 80f04ce..a34d235 100644 (file)
@@ -157,7 +157,7 @@ class GenderCache {
                        return;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $table = [ 'user', 'user_properties' ];
                $fields = [ 'user_name', 'up_value' ];
                $conds = [ 'user_name' => $usersToCheck ];
index ea2e20b..1bab0f5 100644 (file)
  */
 class HTMLFileCache extends FileCacheBase {
        /**
-        * Construct an ObjectFileCache from a Title and an action
+        * Construct an HTMLFileCache object from a Title and an action
+        *
+        * @deprecated since 1.24, instantiate this class directly
         * @param Title|string $title Title object or prefixed DB key string
         * @param string $action
         * @throws MWException
         * @return HTMLFileCache
-        *
-        * @deprecated Since 1.24, instantiate this class directly
         */
        public static function newFromTitle( $title, $action ) {
                return new self( $title, $action );
index ec37dd6..8a4d061 100644 (file)
@@ -40,7 +40,11 @@ class LinkBatch {
         */
        protected $caller;
 
-       function __construct( $arr = [] ) {
+       /**
+        * LinkBatch constructor.
+        * @param LinkTarget[] $arr Initial items to be added to the batch
+        */
+       public function __construct( $arr = [] ) {
                foreach ( $arr as $item ) {
                        $this->addObj( $item );
                }
@@ -188,7 +192,7 @@ class LinkBatch {
                }
 
                // This is similar to LinkHolderArray::replaceInternal
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $table = 'page';
                $fields = array_merge(
                        LinkCache::getSelectFields(),
index 3fd29f3..23cc26d 100644 (file)
@@ -29,19 +29,17 @@ use MediaWiki\MediaWikiServices;
  * @ingroup Cache
  */
 class LinkCache {
-       /**
-        * @var HashBagOStuff
-        */
+       /** @var HashBagOStuff */
        private $mGoodLinks;
-       /**
-        * @var HashBagOStuff
-        */
+       /** @var HashBagOStuff */
        private $mBadLinks;
+       /** @var WANObjectCache */
+       private $wanCache;
+
+       /** @var bool */
        private $mForUpdate = false;
 
-       /**
-        * @var TitleFormatter
-        */
+       /** @var TitleFormatter */
        private $titleFormatter;
 
        /**
@@ -50,9 +48,10 @@ class LinkCache {
         */
        const MAX_SIZE = 10000;
 
-       public function __construct( TitleFormatter $titleFormatter ) {
+       public function __construct( TitleFormatter $titleFormatter, WANObjectCache $cache ) {
                $this->mGoodLinks = new HashBagOStuff( [ 'maxKeys' => self::MAX_SIZE ] );
                $this->mBadLinks = new HashBagOStuff( [ 'maxKeys' => self::MAX_SIZE ] );
+               $this->wanCache = $cache;
                $this->titleFormatter = $titleFormatter;
        }
 
@@ -244,15 +243,31 @@ class LinkCache {
                        return 0;
                }
 
-               // Some fields heavily used for linking...
-               $db = $this->mForUpdate ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+               // Cache template/file pages as they are less often viewed but heavily used
+               if ( $this->mForUpdate ) {
+                       $row = $this->fetchPageRow( wfGetDB( DB_MASTER ), $nt );
+               } elseif ( $this->isCacheable( $nt ) ) {
+                       // These pages are often transcluded heavily, so cache them
+                       $cache = $this->wanCache;
+                       $row = $cache->getWithSetCallback(
+                               $cache->makeKey( 'page', $nt->getNamespace(), sha1( $nt->getDBkey() ) ),
+                               $cache::TTL_DAY,
+                               function ( $curValue, &$ttl, array &$setOpts ) use ( $cache, $nt ) {
+                                       $dbr = wfGetDB( DB_REPLICA );
+                                       $setOpts += Database::getCacheSetOptions( $dbr );
 
-               $row = $db->selectRow( 'page', self::getSelectFields(),
-                       [ 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ],
-                       __METHOD__
-               );
+                                       $row = $this->fetchPageRow( $dbr, $nt );
+                                       $mtime = $row ? wfTimestamp( TS_UNIX, $row->page_touched ) : false;
+                                       $ttl = $cache->adaptiveTTL( $mtime, $ttl );
 
-               if ( $row !== false ) {
+                                       return $row;
+                               }
+                       );
+               } else {
+                       $row = $this->fetchPageRow( wfGetDB( DB_REPLICA ), $nt );
+               }
+
+               if ( $row ) {
                        $this->addGoodLinkObjFromRow( $nt, $row );
                        $id = intval( $row->page_id );
                } else {
@@ -263,6 +278,39 @@ class LinkCache {
                return $id;
        }
 
+       private function isCacheable( LinkTarget $title ) {
+               return ( $title->inNamespace( NS_TEMPLATE ) || $title->inNamespace( NS_FILE ) );
+       }
+
+       private function fetchPageRow( IDatabase $db, LinkTarget $nt ) {
+               $fields = self::getSelectFields();
+               if ( $this->isCacheable( $nt ) ) {
+                       $fields[] = 'page_touched';
+               }
+
+               return $db->selectRow(
+                       'page',
+                       $fields,
+                       [ 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ],
+                       __METHOD__
+               );
+       }
+
+       /**
+        * Purge the link cache for a title
+        *
+        * @param LinkTarget $title
+        * @since 1.28
+        */
+       public function invalidateTitle( LinkTarget $title ) {
+               if ( $this->isCacheable( $title ) ) {
+                       $cache = ObjectCache::getMainWANInstance();
+                       $cache->delete(
+                               $cache->makeKey( 'page', $title->getNamespace(), sha1( $title->getDBkey() ) )
+                       );
+               }
+       }
+
        /**
         * Clears cache
         */
index 279898c..90ad241 100644 (file)
@@ -165,7 +165,7 @@ class MessageBlobStore implements LoggerAwareInterface {
                $cache->set( $cacheKey, $blob,
                        // Add part of a day to TTL to avoid all modules expiring at once
                        $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
-                       Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) )
+                       Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) )
                );
                return $blob;
        }
@@ -179,7 +179,7 @@ class MessageBlobStore implements LoggerAwareInterface {
        public function updateMessage( $key ) {
                $moduleNames = $this->getResourceLoader()->getModulesByMessage( $key );
                foreach ( $moduleNames as $moduleName ) {
-                       // Uses a holdoff to account for database slave lag (for MessageCache)
+                       // Uses a holdoff to account for database replica DB lag (for MessageCache)
                        $this->wanCache->touchCheckKey( $this->wanCache->makeKey( __CLASS__, $moduleName ) );
                }
        }
index 62fab5f..d254d3d 100644 (file)
@@ -302,7 +302,7 @@ class MessageCache {
                                                $where[] = 'global cache is expired';
                                                $staleCache = $cache;
                                        } elseif ( $hashVolatile ) {
-                                               # DB results are slave lag prone until the holdoff TTL passes.
+                                               # DB results are replica DB lag prone until the holdoff TTL passes.
                                                # By then, updates should be reflected in loadFromDBWithLock().
                                                # One thread renerates the cache while others use old values.
                                                $where[] = 'global cache is expired/volatile';
@@ -442,7 +442,7 @@ class MessageCache {
        function loadFromDB( $code, $mode = null ) {
                global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
 
-               $dbr = wfGetDB( ( $mode == self::FOR_UPDATE ) ? DB_MASTER : DB_SLAVE );
+               $dbr = wfGetDB( ( $mode == self::FOR_UPDATE ) ? DB_MASTER : DB_REPLICA );
 
                $cache = [];
 
@@ -564,7 +564,7 @@ class MessageCache {
                }
 
                // Mark this cache as definitely "latest" (non-volatile) so
-               // load() calls do try to refresh the cache with slave data
+               // load() calls do try to refresh the cache with replica DB data
                $this->mCache[$code]['LATEST'] = time();
 
                // Update caches if the lock was acquired
diff --git a/includes/cache/ObjectFileCache.php b/includes/cache/ObjectFileCache.php
deleted file mode 100644 (file)
index c7ef044..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-/**
- * Object cache in 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 Cache
- */
-
-/**
- * Object cache in the file system.
- *
- * @ingroup Cache
- */
-class ObjectFileCache extends FileCacheBase {
-       /**
-        * Construct an ObjectFileCache from a key and a type
-        * @param string $key
-        * @param string $type
-        * @return ObjectFileCache
-        */
-       public static function newFromKey( $key, $type ) {
-               $cache = new self();
-
-               $cache->mKey = (string)$key;
-               $cache->mType = (string)$type;
-
-               return $cache;
-       }
-
-       /**
-        * Get the base file cache directory
-        * @return string
-        */
-       protected function cacheDirectory() {
-               return $this->baseCacheDirectory() . '/object';
-       }
-}
index 48d0398..016306a 100644 (file)
@@ -100,7 +100,7 @@ class UserCache {
 
                // Lookup basic info for users not yet loaded...
                if ( count( $usersToQuery ) ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $table = [ 'user' ];
                        $conds = [ 'user_id' => $usersToQuery ];
                        $fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id' ];
index c350178..e7e2d10 100644 (file)
@@ -39,7 +39,7 @@ class LCStoreDB implements LCStore {
                if ( $this->writesDone && $this->dbw ) {
                        $db = $this->dbw; // see the changes in finishWrite()
                } else {
-                       $db = wfGetDB( DB_SLAVE );
+                       $db = wfGetDB( DB_REPLICA );
                }
 
                $value = $db->selectField(
index 64d8139..515ab05 100644 (file)
@@ -164,7 +164,7 @@ class CategoryMembershipChange {
                /**
                 * T109700 - Default bot flag to true when there is no corresponding RC entry
                 * This means all changes caused by parser functions & Lua on reparse are marked as bot
-                * Also in the case no RC entry could be found due to slave lag
+                * Also in the case no RC entry could be found due to replica DB lag
                 */
                $bot = 1;
                $lastRevId = 0;
index daf76df..a5d1fc5 100644 (file)
@@ -180,7 +180,7 @@ class RecentChange {
        public static function newFromConds(
                $conds,
                $fname = __METHOD__,
-               $dbType = DB_SLAVE
+               $dbType = DB_REPLICA
        ) {
                $db = wfGetDB( $dbType );
                $row = $db->selectRow( 'recentchanges', self::selectFields(), $conds, $fname );
@@ -285,6 +285,17 @@ class RecentChange {
                        $this->mAttribs['rc_ip'] = '';
                }
 
+               # Strict mode fixups (not-NULL fields)
+               foreach ( [ 'minor', 'bot', 'new', 'patrolled', 'deleted' ] as $field ) {
+                       $this->mAttribs["rc_$field"] = (int)$this->mAttribs["rc_$field"];
+               }
+               # ...more fixups (NULL fields)
+               foreach ( [ 'old_len', 'new_len' ] as $field ) {
+                       $this->mAttribs["rc_$field"] = isset( $this->mAttribs["rc_$field"] )
+                               ? (int)$this->mAttribs["rc_$field"]
+                               : null;
+               }
+
                # If our database is strict about IP addresses, use NULL instead of an empty string
                if ( $dbw->strictIPs() && $this->mAttribs['rc_ip'] == '' ) {
                        unset( $this->mAttribs['rc_ip'] );
@@ -776,7 +787,7 @@ class RecentChange {
                        'rc_comment' => $logComment,
                        'rc_this_oldid' => $revId,
                        'rc_last_oldid' => 0,
-                       'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0,
+                       'rc_bot' => $user->isAllowed( 'bot' ) ? (int)$wgRequest->getBool( 'bot', true ) : 0,
                        'rc_ip' => self::checkIPAddress( $ip ),
                        'rc_patrolled' => $markPatrolled ? 1 : 0,
                        'rc_new' => 0, # obsolete
index a2945af..c8c5073 100644 (file)
@@ -174,7 +174,7 @@ class ChangeTags {
 
                // Might as well look for rcids and so on.
                if ( !$rc_id ) {
-                       // Info might be out of date, somewhat fractionally, on slave.
+                       // Info might be out of date, somewhat fractionally, on replica DB.
                        // LogEntry/LogPage and WikiPage match rev/log/rc timestamps,
                        // so use that relation to avoid full table scans.
                        if ( $log_id ) {
@@ -201,7 +201,7 @@ class ChangeTags {
                                );
                        }
                } elseif ( !$log_id && !$rev_id ) {
-                       // Info might be out of date, somewhat fractionally, on slave.
+                       // Info might be out of date, somewhat fractionally, on replica DB.
                        $log_id = $dbw->selectField(
                                'recentchanges',
                                'rc_logid',
@@ -313,7 +313,7 @@ class ChangeTags {
                $tagsToAdd = array_diff( $tagsToAdd, $tagsToRemove );
 
                // Update the summary row.
-               // $prevTags can be out of date on slaves, especially when addTags is called consecutively,
+               // $prevTags can be out of date on replica DBs, especially when addTags is called consecutively,
                // causing loss of tags added recently in tag_summary table.
                $prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
                $prevTags = $prevTags ? $prevTags : '';
@@ -633,7 +633,7 @@ class ChangeTags {
                        throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
                }
 
-               $fields['ts_tags'] = wfGetDB( DB_SLAVE )->buildGroupConcatField(
+               $fields['ts_tags'] = wfGetDB( DB_REPLICA )->buildGroupConcatField(
                        ',', 'change_tag', 'ct_tag', $join_cond
                );
 
@@ -1136,7 +1136,7 @@ class ChangeTags {
                        wfMemcKey( 'active-tags' ),
                        WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) {
-                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
 
                                // Ask extensions which tags they consider active
                                $extensionActive = [];
@@ -1181,7 +1181,7 @@ class ChangeTags {
                        wfMemcKey( 'valid-tags-db' ),
                        WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
-                               $dbr = wfGetDB( DB_SLAVE );
+                               $dbr = wfGetDB( DB_REPLICA );
 
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
@@ -1211,7 +1211,7 @@ class ChangeTags {
                        wfMemcKey( 'valid-tags-hook' ),
                        WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) {
-                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
 
                                $tags = [];
                                Hooks::run( 'ListDefinedTags', [ &$tags ] );
@@ -1266,7 +1266,7 @@ class ChangeTags {
                        wfMemcKey( 'change-tag-statistics' ),
                        WANObjectCache::TTL_MINUTE * 5,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
-                               $dbr = wfGetDB( DB_SLAVE, 'vslow' );
+                               $dbr = wfGetDB( DB_REPLICA, 'vslow' );
 
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
index 41fdef5..4e50c8e 100644 (file)
@@ -453,10 +453,6 @@ abstract class ContentHandler {
        public function __construct( $modelId, $formats ) {
                $this->mModelID = $modelId;
                $this->mSupportedFormats = $formats;
-
-               $this->mModelName = preg_replace( '/(Content)?Handler$/', '', get_class( $this ) );
-               $this->mModelName = preg_replace( '/[_\\\\]/', '', $this->mModelName );
-               $this->mModelName = strtolower( $this->mModelName );
        }
 
        /**
@@ -900,7 +896,7 @@ abstract class ContentHandler {
         * have it / want it.
         */
        public function getAutoDeleteReason( Title $title, &$hasHistory ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Get the last revision
                $rev = Revision::newFromTitle( $title );
@@ -1018,9 +1014,21 @@ abstract class ContentHandler {
                        return false; // no content to undo
                }
 
-               $this->checkModelID( $cur_content->getModel() );
-               $this->checkModelID( $undo_content->getModel() );
-               $this->checkModelID( $undoafter_content->getModel() );
+               try {
+                       $this->checkModelID( $cur_content->getModel() );
+                       $this->checkModelID( $undo_content->getModel() );
+                       if ( $current->getId() !== $undo->getId() ) {
+                               // If we are undoing the most recent revision,
+                               // its ok to revert content model changes. However
+                               // if we are undoing a revision in the middle, then
+                               // doing that will be confusing.
+                               $this->checkModelID( $undoafter_content->getModel() );
+                       }
+               } catch ( MWException $e ) {
+                       // If the revisions have different content models
+                       // just return false
+                       return false;
+               }
 
                if ( $cur_content->equals( $undo_content ) ) {
                        // No use doing a merge if it's just a straight revert.
index eb1c67d..edb21f6 100644 (file)
@@ -39,4 +39,9 @@ class JsonContentHandler extends CodeContentHandler {
        protected function getContentClass() {
                return JsonContent::class;
        }
+
+       public function makeEmptyContent() {
+               $class = $this->getContentClass();
+               return new $class( '{}' );
+       }
 }
index b98174b..cc63446 100644 (file)
@@ -26,7 +26,7 @@
  *
  * @since 1.26
  */
-class DBAccessObjectUtils {
+class DBAccessObjectUtils implements IDBAccessObject {
        /**
         * @param integer $bitfield
         * @param integer $flags IDBAccessObject::READ_* constant
@@ -37,23 +37,45 @@ class DBAccessObjectUtils {
        }
 
        /**
-        * Get an appropriate DB index and options for a query
+        * Get an appropriate DB index, options, and fallback DB index for a query
         *
-        * @param integer $bitfield
-        * @return array (DB_MASTER/DB_SLAVE, SELECT options array)
+        * The fallback DB index and options are to be used if the entity is not found
+        * with the initial DB index, typically querying the master DB to avoid lag
+        *
+        * @param integer $bitfield Bitfield of IDBAccessObject::READ_* constants
+        * @return array List of DB indexes and options in this order:
+        *   - DB_MASTER or DB_REPLICA constant for the initial query
+        *   - SELECT options array for the initial query
+        *   - DB_MASTER constant for the fallback query; null if no fallback should happen
+        *   - SELECT options array for the fallback query; empty if no fallback should happen
         */
        public static function getDBOptions( $bitfield ) {
-               $index = self::hasFlags( $bitfield, IDBAccessObject::READ_LATEST )
-                       ? DB_MASTER
-                       : DB_SLAVE;
+               if ( self::hasFlags( $bitfield, self::READ_LATEST_IMMUTABLE ) ) {
+                       $index = DB_REPLICA; // override READ_LATEST if set
+                       $fallbackIndex = DB_MASTER;
+               } elseif ( self::hasFlags( $bitfield, self::READ_LATEST ) ) {
+                       $index = DB_MASTER;
+                       $fallbackIndex = null;
+               } else {
+                       $index = DB_REPLICA;
+                       $fallbackIndex = null;
+               }
+
+               $lockingOptions = [];
+               if ( self::hasFlags( $bitfield, self::READ_EXCLUSIVE ) ) {
+                       $lockingOptions[] = 'FOR UPDATE';
+               } elseif ( self::hasFlags( $bitfield, self::READ_LOCKING ) ) {
+                       $lockingOptions[] = 'LOCK IN SHARE MODE';
+               }
 
-               $options = [];
-               if ( self::hasFlags( $bitfield, IDBAccessObject::READ_EXCLUSIVE ) ) {
-                       $options[] = 'FOR UPDATE';
-               } elseif ( self::hasFlags( $bitfield, IDBAccessObject::READ_LOCKING ) ) {
-                       $options[] = 'LOCK IN SHARE MODE';
+               if ( $fallbackIndex !== null ) {
+                       $options = []; // locks on DB_REPLICA make no sense
+                       $fallbackOptions = $lockingOptions;
+               } else {
+                       $options = $lockingOptions;
+                       $fallbackOptions = []; // no fallback
                }
 
-               return [ $index, $options ];
+               return [ $index, $options, $fallbackIndex, $fallbackOptions ];
        }
 }
index c24962b..5acf3ae 100644 (file)
  * though certain objects may assume READ_LATEST for common use case or legacy reasons.
  *
  * There are four types of reads:
- *   - READ_NORMAL    : Potentially cached read of data (e.g. from a slave or stale replica)
+ *   - READ_NORMAL    : Potentially cached read of data (e.g. from a replica DB or stale replica)
  *   - READ_LATEST    : Up-to-date read as of transaction start (e.g. from master or a quorum read)
  *   - READ_LOCKING   : Up-to-date read as of now, that locks (shared) the records
  *   - READ_EXCLUSIVE : Up-to-date read as of now, that locks (exclusive) the records
  * All record locks persist for the duration of the transaction.
  *
  * A special constant READ_LATEST_IMMUTABLE can be used for fetching append-only data. Such
- * data is either (a) on a slave and up-to-date or (b) not yet there, but on the master/quorum.
- * Because the data is append-only, it can never be stale on a slave if present.
+ * data is either (a) on a replica DB and up-to-date or (b) not yet there, but on the master/quorum.
+ * Because the data is append-only, it can never be stale on a replica DB if present.
  *
  * Callers should use READ_NORMAL (or pass in no flags) unless the read determines a write.
  * In theory, such cases may require READ_LOCKING, though to avoid contention, READ_LATEST is
@@ -54,7 +54,7 @@
  */
 interface IDBAccessObject {
        /** Constants for object loading bitfield flags (higher => higher QoS) */
-       /** @var integer Read from a slave/non-quorum */
+       /** @var integer Read from a replica DB/non-quorum */
        const READ_NORMAL = 0;
        /** @var integer Read from the master/quorum */
        const READ_LATEST = 1;
@@ -63,7 +63,7 @@ interface IDBAccessObject {
        /** @var integer Read from the master/quorum and lock out other writers and locking readers */
        const READ_EXCLUSIVE = 7; // READ_LOCKING (3) and "FOR UPDATE" (4)
 
-       /** @var integer Read from a slave/non-quorum immutable data, using the master/quorum on miss */
+       /** @var integer Read from a replica DB or without a quorum, using the master/quorum on miss */
        const READ_LATEST_IMMUTABLE = 8;
 
        // Convenience constant for tracking how data was loaded (higher => higher QoS)
index 2539b87..b4619f3 100644 (file)
@@ -79,9 +79,9 @@ class ChronologyProtector {
         * Initialise a LoadBalancer to give it appropriate chronology protection.
         *
         * If the stash has a previous master position recorded, this will try to
-        * make sure that the next query to a slave of that master will see changes up
+        * make sure that the next query to a replica DB of that master will see changes up
         * to that position by delaying execution. The delay may timeout and allow stale
-        * data if no non-lagged slaves are available.
+        * data if no non-lagged replica DBs are available.
         *
         * @param LoadBalancer $lb
         * @return void
index 1019e72..9997f2c 100644 (file)
@@ -469,6 +469,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function flushSnapshot( $fname = __METHOD__ ) {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        public function listTables( $prefix = null, $fname = __METHOD__ ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
index 0a660d9..ced7379 100644 (file)
@@ -128,7 +128,7 @@ abstract class DatabaseBase implements IDatabase {
         */
        private $mTrxTimestamp = null;
        /** @var float Lag estimate at the time of BEGIN */
-       private $mTrxSlaveLag = null;
+       private $mTrxReplicaLag = null;
        /**
         * Remembers the function name given for starting the most recent transaction via begin().
         * Used to provide additional context for error reporting.
@@ -1028,20 +1028,21 @@ abstract class DatabaseBase implements IDatabase {
         * @param float $runtime Total runtime, including RTT
         */
        private function updateTrxWriteQueryTime( $sql, $runtime ) {
-               $indicativeOfSlaveRuntime = true;
+               // Whether this is indicative of replica DB runtime (except for RBR or ws_repl)
+               $indicativeOfReplicaRuntime = true;
                if ( $runtime > self::SLOW_WRITE_SEC ) {
                        $verb = $this->getQueryVerb( $sql );
                        // insert(), upsert(), replace() are fast unless bulky in size or blocked on locks
                        if ( $verb === 'INSERT' ) {
-                               $indicativeOfSlaveRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS;
+                               $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS;
                        } elseif ( $verb === 'REPLACE' ) {
-                               $indicativeOfSlaveRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS / 2;
+                               $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS / 2;
                        }
                }
 
                $this->mTrxWriteDuration += $runtime;
                $this->mTrxWriteQueryCount += 1;
-               if ( $indicativeOfSlaveRuntime ) {
+               if ( $indicativeOfReplicaRuntime ) {
                        $this->mTrxWriteAdjDuration += $runtime;
                        $this->mTrxWriteAdjQueryCount += 1;
                }
@@ -1809,6 +1810,15 @@ abstract class DatabaseBase implements IDatabase {
                return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
        }
 
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return $field;
+       }
+
        public function selectDB( $db ) {
                # Stub. Shouldn't cause serious problems if it's not overridden, but
                # if your database engine supports a concept similar to MySQL's
@@ -2908,10 +2918,10 @@ abstract class DatabaseBase implements IDatabase {
                $this->mTrxWriteAdjQueryCount = 0;
                $this->mTrxWriteCallers = [];
                // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
-               // Get an estimate of the slave lag before then, treating estimate staleness
+               // Get an estimate of the replica DB lag before then, treating estimate staleness
                // as lag itself just to be safe
                $status = $this->getApproximateLagStatus();
-               $this->mTrxSlaveLag = $status['lag'] + ( microtime( true ) - $status['since'] );
+               $this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
        }
 
        /**
@@ -3034,7 +3044,7 @@ abstract class DatabaseBase implements IDatabase {
                }
        }
 
-       public function clearSnapshot( $fname = __METHOD__ ) {
+       public function flushSnapshot( $fname = __METHOD__ ) {
                if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
                        // This only flushes transactions to clear snapshots, not to write data
                        throw new DBUnexpectedError(
@@ -3194,7 +3204,7 @@ abstract class DatabaseBase implements IDatabase {
        }
 
        /**
-        * Get the slave lag when the current transaction started
+        * Get the replica DB lag when the current transaction started
         *
         * This is useful when transactions might use snapshot isolation
         * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
@@ -3206,19 +3216,19 @@ abstract class DatabaseBase implements IDatabase {
         */
        public function getTransactionLagStatus() {
                return $this->mTrxLevel
-                       ? [ 'lag' => $this->mTrxSlaveLag, 'since' => $this->trxTimestamp() ]
+                       ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
                        : null;
        }
 
        /**
-        * Get a slave lag estimate for this server
+        * Get a replica DB lag estimate for this server
         *
         * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
         * @since 1.27
         */
        public function getApproximateLagStatus() {
                return [
-                       'lag'   => $this->getLBInfo( 'slave' ) ? $this->getLag() : 0,
+                       'lag'   => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
                        'since' => microtime( true )
                ];
        }
index 93814d2..e813f80 100644 (file)
  */
 abstract class DatabaseMysqlBase extends Database {
        /** @var MysqlMasterPos */
-       protected $lastKnownSlavePos;
-       /** @var string Method to detect slave lag */
+       protected $lastKnownReplicaPos;
+       /** @var string Method to detect replica DB lag */
        protected $lagDetectionMethod;
-       /** @var array Method to detect slave lag */
+       /** @var array Method to detect replica DB lag */
        protected $lagDetectionOptions = [];
        /** @var bool bool Whether to use GTID methods */
        protected $useGTIDs = false;
@@ -695,7 +695,7 @@ abstract class DatabaseMysqlBase extends Database {
                $key = $cache->makeGlobalKey(
                        'mysql',
                        'master-info',
-                       // Using one key for all cluster slaves is preferable
+                       // Using one key for all cluster replica DBs is preferable
                        $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
                );
 
@@ -771,7 +771,7 @@ abstract class DatabaseMysqlBase extends Database {
 
                if ( $this->getLBInfo( 'is static' ) === true ) {
                        return 0; // this is a copy of a read-only dataset with no master DB
-               } elseif ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
+               } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
                        return 0; // already reached this point for sure
                }
 
@@ -797,16 +797,16 @@ abstract class DatabaseMysqlBase extends Database {
                if ( $status === null ) {
                        // T126436: jobs programmed to wait on master positions might be referencing binlogs
                        // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try
-                       // to detect this and treat the slave as having reached the position; a proper master
+                       // to detect this and treat the replica DB as having reached the position; a proper master
                        // switchover already requires that the new master be caught up before the switch.
-                       $slavePos = $this->getSlavePos();
-                       if ( $slavePos && !$slavePos->channelsMatch( $pos ) ) {
-                               $this->lastKnownSlavePos = $slavePos;
+                       $replicationPos = $this->getSlavePos();
+                       if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) {
+                               $this->lastKnownReplicaPos = $replicationPos;
                                $status = 0;
                        }
                } elseif ( $status >= 0 ) {
                        // Remember that this position was reached to save queries next time
-                       $this->lastKnownSlavePos = $pos;
+                       $this->lastKnownReplicaPos = $pos;
                }
 
                return $status;
index 33a8280..171191b 100644 (file)
@@ -1555,6 +1555,15 @@ class DatabaseOracle extends Database {
                return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
        }
 
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return 'CAST ( ' . $field . ' AS VARCHAR2 )';
+       }
+
        public function getSearchEngine() {
                return 'SearchOracle';
        }
index c8e9ac7..0a178de 100644 (file)
@@ -1535,6 +1535,15 @@ SQL;
                return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
        }
 
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return $field . '::text';
+       }
+
        public function getSearchEngine() {
                return 'SearchPostgres';
        }
@@ -1579,21 +1588,21 @@ SQL;
         */
        public function lock( $lockName, $method, $timeout = 5 ) {
                $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
-               for ( $attempts = 1; $attempts <= $timeout; ++$attempts ) {
-                       $result = $this->query(
-                               "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
-                       $row = $this->fetchObject( $result );
-                       if ( $row->lockstatus === 't' ) {
-                               parent::lock( $lockName, $method, $timeout ); // record
-                               return true;
-                       } else {
-                               sleep( 1 );
-                       }
-               }
+               $loop = new WaitConditionLoop(
+                       function () use ( $lockName, $key, $timeout, $method ) {
+                               $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
+                               $row = $this->fetchObject( $res );
+                               if ( $row->lockstatus === 't' ) {
+                                       parent::lock( $lockName, $method, $timeout ); // record
+                                       return true;
+                               }
 
-               wfDebug( __METHOD__ . " failed to acquire lock\n" );
+                               return WaitConditionLoop::CONDITION_CONTINUE;
+                       },
+                       $timeout
+               );
 
-               return false;
+               return ( $loop->invoke() === $loop::CONDITION_REACHED );
        }
 
        /**
index 5bbba88..e6401b3 100644 (file)
@@ -832,6 +832,15 @@ class DatabaseSqlite extends Database {
                return parent::buildLike( $params ) . "ESCAPE '\' ";
        }
 
+       /**
+        * @param string $field Field or column to cast
+        * @return string
+        * @since 1.28
+        */
+       public function buildStringCast( $field ) {
+               return 'CAST ( ' . $field . ' AS TEXT )';
+       }
+
        /**
         * @return string
         */
index b6c37ee..aeaa27f 100644 (file)
@@ -314,7 +314,7 @@ class LikeMatch {
 }
 
 /**
- * An object representing a master or slave position in a replicated setup.
+ * An object representing a master or replica DB position in a replicated setup.
  *
  * The implementation details of this opaque type are up to the database subclass.
  */
index 5f543c3..f312357 100644 (file)
@@ -1235,20 +1235,20 @@ interface IDatabase {
        public function wasReadOnlyError();
 
        /**
-        * Wait for the slave to catch up to a given master position
+        * Wait for the replica DB to catch up to a given master position
         *
         * @param DBMasterPos $pos
         * @param int $timeout The maximum number of seconds to wait for synchronisation
-        * @return int|null Zero if the slave was past that position already,
+        * @return int|null Zero if the replica DB was past that position already,
         *   greater than zero if we waited for some period of time, less than
         *   zero if it timed out, and null on error
         */
        public function masterPosWait( DBMasterPos $pos, $timeout );
 
        /**
-        * Get the replication position of this slave
+        * Get the replication position of this replica DB
         *
-        * @return DBMasterPos|bool False if this is not a slave.
+        * @return DBMasterPos|bool False if this is not a replica DB.
         */
        public function getSlavePos();
 
@@ -1420,7 +1420,7 @@ interface IDatabase {
         * will cause a warning, unless the current transaction was started
         * automatically because of the DBO_TRX flag.
         *
-        * @param string $fname
+        * @param string $fname Calling function name
         * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
         * @throws DBError
         */
@@ -1458,7 +1458,7 @@ interface IDatabase {
         * throwing an Exception is preferrable, using a pre-installed error handler to trigger
         * rollback (in any case, failure to issue COMMIT will cause rollback server-side).
         *
-        * @param string $fname
+        * @param string $fname Calling function name
         * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_*
         *   constant to disable warnings about calling rollback when no transaction is in
         *   progress. This will silently break any ongoing explicit transaction. Only set the
@@ -1468,6 +1468,20 @@ interface IDatabase {
         */
        public function rollback( $fname = __METHOD__, $flush = '' );
 
+       /**
+        * Commit any transaction but error out if writes or callbacks are pending
+        *
+        * This is intended for clearing out REPEATABLE-READ snapshots so that callers can
+        * see a new point-in-time of the database. This is useful when one of many transaction
+        * rounds finished and significant time will pass in the script's lifetime. It is also
+        * useful to call on a replica DB after waiting on replication to catch up to the master.
+        *
+        * @param string $fname Calling function name
+        * @throws DBUnexpectedError
+        * @since 1.28
+        */
+       public function flushSnapshot( $fname = __METHOD__ );
+
        /**
         * List all tables on the database
         *
@@ -1515,7 +1529,7 @@ interface IDatabase {
        public function ping( &$rtt = null );
 
        /**
-        * Get slave lag. Currently supported only by MySQL.
+        * Get replica DB lag. Currently supported only by MySQL.
         *
         * Note that this function will generate a fatal error on many
         * installations. Most callers should use LoadBalancer::safeGetLag()
@@ -1526,7 +1540,7 @@ interface IDatabase {
        public function getLag();
 
        /**
-        * Get the slave lag when the current transaction started
+        * Get the replica DB lag when the current transaction started
         * or a general lag estimate if not transaction is active
         *
         * This is useful when transactions might use snapshot isolation
index 2e6f602..62a5286 100644 (file)
@@ -185,7 +185,7 @@ abstract class LBFactory implements DestructibleService {
         * @param bool|string $wiki Wiki ID, or false for the current wiki
         * @return LoadBalancer
         */
-       abstract public function &getExternalLB( $cluster, $wiki = false );
+       abstract public function getExternalLB( $cluster, $wiki = false );
 
        /**
         * Execute a function for each tracked load balancer
@@ -223,6 +223,29 @@ abstract class LBFactory implements DestructibleService {
                );
        }
 
+       /**
+        * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
+        *
+        * @param string $fname Caller name
+        * @since 1.28
+        */
+       public function flushReplicaSnapshots( $fname = __METHOD__ ) {
+               $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
+       }
+
+       /**
+        * Commit on all connections. Done for two reasons:
+        * 1. To commit changes to the masters.
+        * 2. To release the snapshot on all connections, master and replica DB.
+        * @param string $fname Caller name
+        * @param array $options Options map:
+        *   - maxWriteDuration: abort if more than this much time was spent in write queries
+        */
+       public function commitAll( $fname = __METHOD__, array $options = [] ) {
+               $this->commitMasterChanges( $fname, $options );
+               $this->forEachLBCallMethod( 'commitAll', [ $fname ] );
+       }
+
        /**
         * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
         *
@@ -230,6 +253,7 @@ abstract class LBFactory implements DestructibleService {
         *   - commitMasterChanges()
         *   - rollbackMasterChanges()
         *   - commitAll()
+        *
         * This allows for custom transaction rounds from any outer transaction scope.
         *
         * @param string $fname
@@ -240,7 +264,7 @@ abstract class LBFactory implements DestructibleService {
                if ( $this->trxRoundId !== false ) {
                        throw new DBTransactionError(
                                null,
-                               "Transaction round '{$this->trxRoundId}' already started."
+                               "$fname: transaction round '{$this->trxRoundId}' already started."
                        );
                }
                $this->trxRoundId = $fname;
@@ -248,19 +272,6 @@ abstract class LBFactory implements DestructibleService {
                $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] );
        }
 
-       /**
-        * Commit on all connections. Done for two reasons:
-        * 1. To commit changes to the masters.
-        * 2. To release the snapshot on all connections, master and slave.
-        * @param string $fname Caller name
-        * @param array $options Options map:
-        *   - maxWriteDuration: abort if more than this much time was spent in write queries
-        */
-       public function commitAll( $fname = __METHOD__, array $options = [] ) {
-               $this->commitMasterChanges( $fname, $options );
-               $this->forEachLBCallMethod( 'commitAll', [ $fname ] );
-       }
-
        /**
         * Commit changes on all master connections
         * @param string $fname Caller name
@@ -269,6 +280,12 @@ abstract class LBFactory implements DestructibleService {
         * @throws Exception
         */
        public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
+               if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
+                       throw new DBTransactionError(
+                               null,
+                               "$fname: transaction round '{$this->trxRoundId}' still running."
+                       );
+               }
                // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
                $this->forEachLBCallMethod( 'finalizeMasterChanges' );
                $this->trxRoundId = false;
@@ -346,19 +363,28 @@ abstract class LBFactory implements DestructibleService {
        }
 
        /**
-        * Detemine if any lagged slave connection was used
-        * @since 1.27
+        * Detemine if any lagged replica DB connection was used
         * @return bool
+        * @since 1.28
         */
-       public function laggedSlaveUsed() {
+       public function laggedReplicaUsed() {
                $ret = false;
                $this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) {
-                       $ret = $ret || $lb->laggedSlaveUsed();
+                       $ret = $ret || $lb->laggedReplicaUsed();
                } );
 
                return $ret;
        }
 
+       /**
+        * @return bool
+        * @since 1.27
+        * @deprecated Since 1.28; use laggedReplicaUsed()
+        */
+       public function laggedSlaveUsed() {
+               return $this->laggedReplicaUsed();
+       }
+
        /**
         * Determine if any master connection has pending/written changes from this request
         * @return bool
@@ -373,10 +399,10 @@ abstract class LBFactory implements DestructibleService {
        }
 
        /**
-        * Waits for the slave DBs to catch up to the current master position
+        * Waits for the replica DBs to catch up to the current master position
         *
         * Use this when updating very large numbers of rows, as in maintenance scripts,
-        * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
+        * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
         *
         * By default this waits on all DB clusters actually used in this request.
         * This makes sense when lag being waiting on is caused by the code that does this check.
@@ -430,7 +456,7 @@ abstract class LBFactory implements DestructibleService {
                $masterPositions = array_fill( 0, count( $lbs ), false );
                foreach ( $lbs as $i => $lb ) {
                        if ( $lb->getServerCount() <= 1 ) {
-                               // Bug 27975 - Don't try to wait for slaves if there are none
+                               // Bug 27975 - Don't try to wait for replica DBs if there are none
                                // Prevents permission error when getting master position
                                continue;
                        } elseif ( $opts['ifWritesSince']
@@ -454,7 +480,7 @@ abstract class LBFactory implements DestructibleService {
 
                if ( $failed ) {
                        throw new DBReplicationWaitError(
-                               "Could not wait for slaves to catch up to " .
+                               "Could not wait for replica DBs to catch up to " .
                                implode( ', ', $failed )
                        );
                }
@@ -499,7 +525,7 @@ abstract class LBFactory implements DestructibleService {
         * This will commit and wait unless $ticket indicates it is unsafe to do so
         *
         * @param string $fname Caller name (e.g. __METHOD__)
-        * @param mixed $ticket Result of getOuterTransactionScopeTicket()
+        * @param mixed $ticket Result of getEmptyTransactionTicket()
         * @param array $opts Options to waitForReplication()
         * @throws DBReplicationWaitError
         * @since 1.28
@@ -511,8 +537,22 @@ abstract class LBFactory implements DestructibleService {
                        return;
                }
 
-               $this->commitMasterChanges( $fname );
+               // The transaction owner and any caller with the empty transaction ticket can commit
+               // so that getEmptyTransactionTicket() callers don't risk seeing DBTransactionError.
+               if ( $this->trxRoundId !== false && $fname !== $this->trxRoundId ) {
+                       $this->trxLogger->info( "$fname: committing on behalf of {$this->trxRoundId}." );
+                       $fnameEffective = $this->trxRoundId;
+               } else {
+                       $fnameEffective = $fname;
+               }
+
+               $this->commitMasterChanges( $fnameEffective );
                $this->waitForReplication( $opts );
+               // If a nested caller committed on behalf of $fname, start another empty $fname
+               // transaction, leaving the caller with the same empty transaction state as before.
+               if ( $fnameEffective !== $fname ) {
+                       $this->beginMasterChanges( $fnameEffective );
+               }
        }
 
        /**
@@ -560,7 +600,7 @@ abstract class LBFactory implements DestructibleService {
                // Write them to the stash
                $unsavedPositions = $cp->shutdown();
                // If the positions failed to write to the stash, at least wait on local datacenter
-               // slaves to catch up before responding. Even if there are several DCs, this increases
+               // replica DBs to catch up before responding. Even if there are several DCs, this increases
                // the chance that the user will see their own changes immediately afterwards. As long
                // as the sticky DC cookie applies (same domain), this is not even an issue.
                $this->forEachLB( function ( LoadBalancer $lb ) use ( $unsavedPositions ) {
index 33ee250..5cd1d4b 100644 (file)
@@ -40,7 +40,7 @@ class LBFactoryFake extends LBFactory {
                throw new DBAccessError;
        }
 
-       public function &getExternalLB( $cluster, $wiki = false ) {
+       public function getExternalLB( $cluster, $wiki = false ) {
                throw new DBAccessError;
        }
 
index c0f6509..e56631d 100644 (file)
@@ -293,7 +293,7 @@ class LBFactoryMulti extends LBFactory {
         * @param bool|string $wiki Wiki ID, or false for the current wiki
         * @return LoadBalancer
         */
-       public function &getExternalLB( $cluster, $wiki = false ) {
+       public function getExternalLB( $cluster, $wiki = false ) {
                if ( !isset( $this->extLBs[$cluster] ) ) {
                        $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
                        $this->extLBs[$cluster]->parentInfo( [ 'id' => "ext-$cluster" ] );
@@ -353,7 +353,7 @@ class LBFactoryMulti extends LBFactory {
                                }
                                $master = false;
                        } else {
-                               $serverInfo['slave'] = true;
+                               $serverInfo['replica'] = true;
                        }
                        if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
                                $serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
index c06b5b1..4632b0a 100644 (file)
@@ -54,7 +54,7 @@ class LBFactorySimple extends LBFactory {
                                if ( $i == 0 ) {
                                        $server['master'] = true;
                                } else {
-                                       $server['slave'] = true;
+                                       $server['replica'] = true;
                                }
                                $server += [ 'flags' => DBO_DEFAULT ];
                        }
@@ -122,7 +122,7 @@ class LBFactorySimple extends LBFactory {
         * @param bool|string $wiki
         * @return array
         */
-       public function &getExternalLB( $cluster, $wiki = false ) {
+       public function getExternalLB( $cluster, $wiki = false ) {
                if ( !isset( $this->extLBs[$cluster] ) ) {
                        $this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
                        $this->extLBs[$cluster]->parentInfo( [ 'id' => "ext-$cluster" ] );
index 14c1c28..14cec0e 100644 (file)
@@ -73,7 +73,7 @@ class LBFactorySingle extends LBFactory {
         * @param bool|string $wiki Wiki ID, or false for the current wiki
         * @return LoadBalancerSingle
         */
-       public function &getExternalLB( $cluster, $wiki = false ) {
+       public function getExternalLB( $cluster, $wiki = false ) {
                return $this->lb;
        }
 
index c554f17..1f4b993 100644 (file)
@@ -36,9 +36,9 @@ class LoadBalancer {
        private $mLoads;
        /** @var array[] Map of (group => server index => weight) */
        private $mGroupLoads;
-       /** @var bool Whether to disregard slave lag as a factor in slave selection */
+       /** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
        private $mAllowLagged;
-       /** @var integer Seconds to spend waiting on slave lag to resolve */
+       /** @var integer Seconds to spend waiting on replica DB lag to resolve */
        private $mWaitTimeout;
        /** @var array LBFactory information */
        private $mParentInfo;
@@ -56,14 +56,14 @@ class LoadBalancer {
 
        /** @var bool|DatabaseBase Database connection that caused a problem */
        private $mErrorConnection;
-       /** @var integer The generic (not query grouped) slave index (of $mServers) */
+       /** @var integer The generic (not query grouped) replica DB index (of $mServers) */
        private $mReadIndex;
        /** @var bool|DBMasterPos False if not set */
        private $mWaitForPos;
-       /** @var bool Whether the generic reader fell back to a lagged slave */
-       private $laggedSlaveMode = false;
-       /** @var bool Whether the generic reader fell back to a lagged slave */
-       private $slavesDownMode = false;
+       /** @var bool Whether the generic reader fell back to a lagged replica DB */
+       private $laggedReplicaMode = false;
+       /** @var bool Whether the generic reader fell back to a lagged replica DB */
+       private $allReplicasDownMode = false;
        /** @var string The last DB selection or connection error */
        private $mLastError = 'Unknown error';
        /** @var string|bool Reason the LB is read-only or false if not */
@@ -78,8 +78,8 @@ class LoadBalancer {
        /** @var integer Warn when this many connection are held */
        const CONN_HELD_WARN_THRESHOLD = 10;
        /** @var integer Default 'max lag' when unspecified */
-       const MAX_LAG = 10;
-       /** @var integer Max time to wait for a slave to catch up (e.g. ChronologyProtector) */
+       const MAX_LAG_DEFAULT = 10;
+       /** @var integer Max time to wait for a replica DB to catch up (e.g. ChronologyProtector) */
        const POS_WAIT_TIMEOUT = 10;
        /** @var integer Seconds to cache master server read-only status */
        const TTL_CACHE_READONLY = 5;
@@ -94,6 +94,7 @@ class LoadBalancer {
         *  - servers : Required. Array of server info structures.
         *  - loadMonitor : Name of a class used to fetch server lag and load.
         *  - readOnlyReason : Reason the master DB is read-only if so [optional]
+        *  - waitTimeout : Maximum time to wait for replicas for consistency [optional]
         *  - srvCache : BagOStuff object [optional]
         *  - wanCache : WANObjectCache object [optional]
         * @throws MWException
@@ -103,7 +104,9 @@ class LoadBalancer {
                        throw new MWException( __CLASS__ . ': missing servers parameter' );
                }
                $this->mServers = $params['servers'];
-               $this->mWaitTimeout = self::POS_WAIT_TIMEOUT;
+               $this->mWaitTimeout = isset( $params['waitTimeout'] )
+                       ? $params['waitTimeout']
+                       : self::POS_WAIT_TIMEOUT;
 
                $this->mReadIndex = -1;
                $this->mWriteIndex = -1;
@@ -189,19 +192,21 @@ class LoadBalancer {
         * @param int $maxLag Restrict the maximum allowed lag to this many seconds
         * @return bool|int|string
         */
-       private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = self::MAX_LAG ) {
+       private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
                $lags = $this->getLagTimes( $wiki );
 
                # Unset excessively lagged servers
                foreach ( $lags as $i => $lag ) {
                        if ( $i != 0 ) {
-                               $maxServerLag = $maxLag;
-                               if ( isset( $this->mServers[$i]['max lag'] ) ) {
-                                       $maxServerLag = min( $maxServerLag, $this->mServers[$i]['max lag'] );
-                               }
+                               # How much lag this server nominally is allowed to have
+                               $maxServerLag = isset( $this->mServers[$i]['max lag'] )
+                                       ? $this->mServers[$i]['max lag']
+                                       : self::MAX_LAG_DEFAULT; // default
+                               # Constrain that futher by $maxLag argument
+                               $maxServerLag = min( $maxServerLag, $maxLag );
 
                                $host = $this->getServerName( $i );
-                               if ( $lag === false ) {
+                               if ( $lag === false && !is_infinite( $maxServerLag ) ) {
                                        wfDebugLog( 'replication', "Server $host (#$i) is not replicating?" );
                                        unset( $loads[$i] );
                                } elseif ( $lag > $maxServerLag ) {
@@ -211,16 +216,16 @@ class LoadBalancer {
                        }
                }
 
-               # Find out if all the slaves with non-zero load are lagged
+               # Find out if all the replica DBs with non-zero load are lagged
                $sum = 0;
                foreach ( $loads as $load ) {
                        $sum += $load;
                }
                if ( $sum == 0 ) {
-                       # No appropriate DB servers except maybe the master and some slaves with zero load
+                       # No appropriate DB servers except maybe the master and some replica DBs with zero load
                        # Do NOT use the master
                        # Instead, this function will return false, triggering read-only mode,
-                       # and a lagged slave will be used instead.
+                       # and a lagged replica DB will be used instead.
                        return false;
                }
 
@@ -233,7 +238,7 @@ class LoadBalancer {
        }
 
        /**
-        * Get the index of the reader connection, which may be a slave
+        * Get the index of the reader connection, which may be a replica DB
         * This takes into account load ratios and lag times. It should
         * always return a consistent index during a given invocation
         *
@@ -280,16 +285,15 @@ class LoadBalancer {
                # Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
                $this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $wiki );
 
-               $laggedSlaveMode = false;
+               $laggedReplicaMode = false;
 
                # No server found yet
                $i = false;
-               $conn = false;
                # First try quickly looking through the available servers for a server that
                # meets our criteria
                $currentLoads = $nonErrorLoads;
                while ( count( $currentLoads ) ) {
-                       if ( $this->mAllowLagged || $laggedSlaveMode ) {
+                       if ( $this->mAllowLagged || $laggedReplicaMode ) {
                                $i = ArrayUtils::pickRandom( $currentLoads );
                        } else {
                                $i = false;
@@ -306,10 +310,10 @@ class LoadBalancer {
                                        $i = $this->getRandomNonLagged( $currentLoads, $wiki );
                                }
                                if ( $i === false && count( $currentLoads ) != 0 ) {
-                                       # All slaves lagged. Switch to read-only mode
-                                       wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode" );
+                                       # All replica DBs lagged. Switch to read-only mode
+                                       wfDebugLog( 'replication', "All replica DBs lagged. Switch to read-only mode" );
                                        $i = ArrayUtils::pickRandom( $currentLoads );
-                                       $laggedSlaveMode = true;
+                                       $laggedReplicaMode = true;
                                }
                        }
 
@@ -350,18 +354,16 @@ class LoadBalancer {
                }
 
                if ( $i !== false ) {
-                       # Slave connection successful
-                       # Wait for the session master pos for a short time
+                       # Replica DB connection successful.
+                       # Wait for the session master pos for a short time.
                        if ( $this->mWaitForPos && $i > 0 ) {
-                               if ( !$this->doWait( $i ) ) {
-                                       $this->mServers[$i]['slave pos'] = $conn->getSlavePos();
-                               }
+                               $this->doWait( $i );
                        }
                        if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
                                $this->mReadIndex = $i;
-                               # Record if the generic reader index is in "lagged slave" mode
-                               if ( $laggedSlaveMode ) {
-                                       $this->laggedSlaveMode = true;
+                               # Record if the generic reader index is in "lagged replica DB" mode
+                               if ( $laggedReplicaMode ) {
+                                       $this->laggedReplicaMode = true;
                                }
                        }
                        $serverName = $this->getServerName( $i );
@@ -374,7 +376,7 @@ class LoadBalancer {
 
        /**
         * Set the master wait position
-        * If a DB_SLAVE connection has been opened already, waits
+        * If a DB_REPLICA connection has been opened already, waits
         * Otherwise sets a variable telling it to wait if such a connection is opened
         * @param DBMasterPos $pos
         */
@@ -384,14 +386,13 @@ class LoadBalancer {
 
                if ( $i > 0 ) {
                        if ( !$this->doWait( $i ) ) {
-                               $this->mServers[$i]['slave pos'] = $this->getAnyOpenConnection( $i )->getSlavePos();
-                               $this->laggedSlaveMode = true;
+                               $this->laggedReplicaMode = true;
                        }
                }
        }
 
        /**
-        * Set the master wait position and wait for a "generic" slave to catch up to it
+        * Set the master wait position and wait for a "generic" replica DB to catch up to it
         *
         * This can be used a faster proxy for waitForAll()
         *
@@ -405,9 +406,9 @@ class LoadBalancer {
 
                $i = $this->mReadIndex;
                if ( $i <= 0 ) {
-                       // Pick a generic slave if there isn't one yet
+                       // Pick a generic replica DB if there isn't one yet
                        $readLoads = $this->mLoads;
-                       unset( $readLoads[$this->getWriterIndex()] ); // slaves only
+                       unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
                        $readLoads = array_filter( $readLoads ); // with non-zero load
                        $i = ArrayUtils::pickRandom( $readLoads );
                }
@@ -422,7 +423,7 @@ class LoadBalancer {
        }
 
        /**
-        * Set the master wait position and wait for ALL slaves to catch up to it
+        * Set the master wait position and wait for ALL replica DBs to catch up to it
         * @param DBMasterPos $pos
         * @param int $timeout Max seconds to wait; default is mWaitTimeout
         * @return bool Success (able to connect and no timeouts reached)
@@ -459,7 +460,7 @@ class LoadBalancer {
        }
 
        /**
-        * Wait for a given slave to catch up to the master pos stored in $this
+        * Wait for a given replica DB to catch up to the master pos stored in $this
         * @param int $index Server index
         * @param bool $open Check the server even if a new connection has to be made
         * @param int $timeout Max seconds to wait; default is mWaitTimeout
@@ -475,7 +476,7 @@ class LoadBalancer {
                $knownReachedPos = $this->srvCache->get( $key );
                if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) {
                        wfDebugLog( 'replication', __METHOD__ .
-                               ": slave $server known to be caught up (pos >= $knownReachedPos).\n" );
+                               ": replica DB $server known to be caught up (pos >= $knownReachedPos).\n" );
                        return true;
                }
 
@@ -499,12 +500,12 @@ class LoadBalancer {
                        }
                }
 
-               wfDebugLog( 'replication', __METHOD__ . ": Waiting for slave $server to catch up...\n" );
+               wfDebugLog( 'replication', __METHOD__ . ": Waiting for replica DB $server to catch up...\n" );
                $timeout = $timeout ?: $this->mWaitTimeout;
                $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
 
                if ( $result == -1 || is_null( $result ) ) {
-                       // Timed out waiting for slave, use master instead
+                       // Timed out waiting for replica DB, use master instead
                        $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
                        wfDebugLog( 'replication', "$msg\n" );
                        wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
@@ -565,7 +566,7 @@ class LoadBalancer {
                }
 
                # Operation-based index
-               if ( $i == DB_SLAVE ) {
+               if ( $i == DB_REPLICA ) {
                        $this->mLastError = 'Unknown error'; // reset error string
                        # Try the general server pool if $groups are unavailable.
                        $i = in_array( false, $groups, true )
@@ -573,7 +574,7 @@ class LoadBalancer {
                                : $this->getReaderIndex( false, $wiki );
                        # Couldn't find a working server in getReaderIndex()?
                        if ( $i === false ) {
-                               $this->mLastError = 'No working slave server: ' . $this->mLastError;
+                               $this->mLastError = 'No working replica DB server: ' . $this->mLastError;
 
                                return $this->reportConnectionError();
                        }
@@ -613,11 +614,10 @@ class LoadBalancer {
                $serverIndex = $conn->getLBInfo( 'serverIndex' );
                $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
                if ( $serverIndex === null || $refCount === null ) {
-                       wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" );
                        /**
                         * This can happen in code like:
                         *   foreach ( $dbs as $db ) {
-                        *     $conn = $lb->getConnection( DB_SLAVE, [], $db );
+                        *     $conn = $lb->getConnection( DB_REPLICA, [], $db );
                         *     ...
                         *     $lb->reuseConnection( $conn );
                         *   }
@@ -867,11 +867,11 @@ class LoadBalancer {
                        $this->getLazyConnectionRef( DB_MASTER, [], $db->getWikiID() )
                );
                $db->setTransactionProfiler( $this->trxProfiler );
-               if ( $this->trxRoundId !== false ) {
-                       $this->applyTransactionRoundFlags( $db );
-               }
 
                if ( $server['serverIndex'] === $this->getWriterIndex() ) {
+                       if ( $this->trxRoundId !== false ) {
+                               $this->applyTransactionRoundFlags( $db );
+                       }
                        foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
                                $db->setTransactionListener( $name, $callback );
                        }
@@ -997,8 +997,8 @@ class LoadBalancer {
         * @return mixed
         */
        public function getMasterPos() {
-               # If this entire request was served from a slave without opening a connection to the
-               # master (however unlikely that may be), then we can fetch the position from the slave.
+               # If this entire request was served from a replica DB without opening a connection to the
+               # master (however unlikely that may be), then we can fetch the position from the replica DB.
                $masterConn = $this->getAnyOpenConnection( 0 );
                if ( !$masterConn ) {
                        $serverCount = count( $this->mServers );
@@ -1181,7 +1181,7 @@ class LoadBalancer {
                        function ( DatabaseBase $conn ) use ( $fname, &$failures ) {
                                $conn->setTrxEndCallbackSuppression( true );
                                try {
-                                       $conn->clearSnapshot( $fname );
+                                       $conn->flushSnapshot( $fname );
                                } catch ( DBError $e ) {
                                        MWExceptionHandler::logException( $e );
                                        $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
@@ -1215,7 +1215,7 @@ class LoadBalancer {
                                        if ( $conn->writesOrCallbacksPending() ) {
                                                $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
                                        } elseif ( $restore ) {
-                                               $conn->clearSnapshot( $fname );
+                                               $conn->flushSnapshot( $fname );
                                        }
                                } catch ( DBError $e ) {
                                        MWExceptionHandler::logException( $e );
@@ -1244,9 +1244,21 @@ class LoadBalancer {
        public function runMasterPostTrxCallbacks( $type ) {
                $e = null; // first exception
                $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $type, &$e ) {
-                       $conn->clearSnapshot( __METHOD__ ); // clear no-op transactions
-
                        $conn->setTrxEndCallbackSuppression( false );
+                       if ( $conn->writesOrCallbacksPending() ) {
+                               // This happens if onTransactionIdle() callbacks leave callbacks on *another* DB
+                               // (which finished its callbacks already). Warn and recover in this case. Let the
+                               // callbacks run in the final commitMasterChanges() in LBFactory::shutdown().
+                               wfWarn( __METHOD__ . ": did not expect writes/callbacks pending." );
+                               return;
+                       } elseif ( $conn->trxLevel() ) {
+                               // This happens for single-DB setups where DB_REPLICA uses the master DB,
+                               // thus leaving an implicit read-only transaction open at this point. It
+                               // also happens if onTransactionIdle() callbacks leave implicit transactions
+                               // open on *other* DBs (which is slightly improper). Let these COMMIT on the
+                               // next call to commitMasterChanges(), possibly in LBFactory::shutdown().
+                               return;
+                       }
                        try {
                                $conn->runOnTransactionIdleCallbacks( $type );
                        } catch ( Exception $ex ) {
@@ -1317,6 +1329,18 @@ class LoadBalancer {
                }
        }
 
+       /**
+        * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
+        *
+        * @param string $fname Caller name
+        * @since 1.28
+        */
+       public function flushReplicaSnapshots( $fname = __METHOD__ ) {
+               $this->forEachOpenReplicaConnection( function ( DatabaseBase $conn ) {
+                       $conn->flushSnapshot( __METHOD__ );
+               } );
+       }
+
        /**
         * @return bool Whether a master connection is already open
         * @since 1.24
@@ -1338,7 +1362,7 @@ class LoadBalancer {
                        }
                        /** @var DatabaseBase $conn */
                        foreach ( $conns2[$masterIndex] as $conn ) {
-                               if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
+                               if ( $conn->writesOrCallbacksPending() ) {
                                        return true;
                                }
                        }
@@ -1404,44 +1428,53 @@ class LoadBalancer {
                return $fnames;
        }
 
-       /**
-        * @param mixed $value
-        * @return mixed
-        */
-       public function waitTimeout( $value = null ) {
-               return wfSetVar( $this->mWaitTimeout, $value );
-       }
-
        /**
         * @note This method will trigger a DB connection if not yet done
-        *
         * @param string|bool $wiki Wiki ID, or false for the current wiki
         * @return bool Whether the generic connection for reads is highly "lagged"
         */
-       public function getLaggedSlaveMode( $wiki = false ) {
+       public function getLaggedReplicaMode( $wiki = false ) {
                // No-op if there is only one DB (also avoids recursion)
-               if ( !$this->laggedSlaveMode && $this->getServerCount() > 1 ) {
+               if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
                        try {
-                               // See if laggedSlaveMode gets set
-                               $conn = $this->getConnection( DB_SLAVE, false, $wiki );
+                               // See if laggedReplicaMode gets set
+                               $conn = $this->getConnection( DB_REPLICA, false, $wiki );
                                $this->reuseConnection( $conn );
                        } catch ( DBConnectionError $e ) {
                                // Avoid expensive re-connect attempts and failures
-                               $this->slavesDownMode = true;
-                               $this->laggedSlaveMode = true;
+                               $this->allReplicasDownMode = true;
+                               $this->laggedReplicaMode = true;
                        }
                }
 
-               return $this->laggedSlaveMode;
+               return $this->laggedReplicaMode;
+       }
+
+       /**
+        * @param bool $wiki
+        * @return bool
+        * @deprecated 1.28; use getLaggedReplicaMode()
+        */
+       public function getLaggedSlaveMode( $wiki = false ) {
+               return $this->getLaggedReplicaMode( $wiki );
        }
 
        /**
         * @note This method will never cause a new DB connection
         * @return bool Whether any generic connection used for reads was highly "lagged"
+        * @since 1.28
+        */
+       public function laggedReplicaUsed() {
+               return $this->laggedReplicaMode;
+       }
+
+       /**
+        * @return bool
         * @since 1.27
+        * @deprecated Since 1.28; use laggedReplicaUsed()
         */
        public function laggedSlaveUsed() {
-               return $this->laggedSlaveMode;
+               return $this->laggedReplicaUsed();
        }
 
        /**
@@ -1454,13 +1487,13 @@ class LoadBalancer {
        public function getReadOnlyReason( $wiki = false, DatabaseBase $conn = null ) {
                if ( $this->readOnlyReason !== false ) {
                        return $this->readOnlyReason;
-               } elseif ( $this->getLaggedSlaveMode( $wiki ) ) {
-                       if ( $this->slavesDownMode ) {
+               } elseif ( $this->getLaggedReplicaMode( $wiki ) ) {
+                       if ( $this->allReplicasDownMode ) {
                                return 'The database has been automatically locked ' .
-                                       'until the slave database servers become available';
+                                       'until the replica database servers become available';
                        } else {
                                return 'The database has been automatically locked ' .
-                                       'while the slave database servers catch up to the master.';
+                                       'while the replica database servers catch up to the master.';
                        }
                } elseif ( $this->masterRunningReadOnly( $wiki, $conn ) ) {
                        return 'The database master is running in read-only mode.';
@@ -1560,10 +1593,30 @@ class LoadBalancer {
        }
 
        /**
-        * Get the hostname and lag time of the most-lagged slave
+        * Call a function with each open replica DB connection object
+        * @param callable $callback
+        * @param array $params
+        * @since 1.28
+        */
+       public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
+               foreach ( $this->mConns as $connsByServer ) {
+                       foreach ( $connsByServer as $i => $serverConns ) {
+                               if ( $i === $this->getWriterIndex() ) {
+                                       continue; // skip master
+                               }
+                               foreach ( $serverConns as $conn ) {
+                                       $mergedParams = array_merge( [ $conn ], $params );
+                                       call_user_func_array( $callback, $mergedParams );
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Get the hostname and lag time of the most-lagged replica DB
         *
         * This is useful for maintenance scripts that need to throttle their updates.
-        * May attempt to open connections to slaves on the default DB. If there is
+        * May attempt to open connections to replica DBs on the default DB. If there is
         * no lag, the maximum lag will be reported as -1.
         *
         * @param bool|string $wiki Wiki ID, or false for the default database
@@ -1632,19 +1685,19 @@ class LoadBalancer {
        }
 
        /**
-        * Wait for a slave DB to reach a specified master position
+        * Wait for a replica DB to reach a specified master position
         *
         * This will connect to the master to get an accurate position if $pos is not given
         *
-        * @param IDatabase $conn Slave DB
+        * @param IDatabase $conn Replica DB
         * @param DBMasterPos|bool $pos Master position; default: current position
         * @param integer $timeout Timeout in seconds
         * @return bool Success
         * @since 1.27
         */
        public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
-               if ( $this->getServerCount() == 1 || !$conn->getLBInfo( 'slave' ) ) {
-                       return true; // server is not a slave DB
+               if ( $this->getServerCount() == 1 || !$conn->getLBInfo( 'replica' ) ) {
+                       return true; // server is not a replica DB
                }
 
                $pos = $pos ?: $this->getConnection( DB_MASTER )->getMasterPos();
index 24c7930..2b2b2b7 100644 (file)
@@ -27,8 +27,11 @@ use MediaWiki\MediaWikiServices;
  * In web request mode, deferred updates can be run at the end of the request, either before or
  * after the HTTP response has been sent. In either case, they run after the DB commit step. If
  * an update runs after the response is sent, it will not block clients. If sent before, it will
- * run synchronously. If such an update works via queueing, it will be more likely to complete by
- * the time the client makes their next request after this one.
+ * run synchronously. These two modes are defined via PRESEND and POSTSEND constants, the latter
+ * being the default for addUpdate() and addCallableUpdate().
+ *
+ * Updates that work through this system will be more likely to complete by the time the client
+ * makes their next request after this one than with the JobQueue system.
  *
  * In CLI mode, updates run immediately if no DB writes are pending. Otherwise, they run when:
  *   - a) Any waitForReplication() call if no writes are pending on any DB
@@ -36,7 +39,11 @@ use MediaWiki\MediaWikiServices;
  *   - c) EnqueueableDataUpdate tasks may enqueue on commit of Maintenance::getDB( DB_MASTER )
  *   - d) At the completion of Maintenance::execute()
  *
- * When updates are deferred, they use a FIFO queue (one for pre-send and one for post-send).
+ * When updates are deferred, they go into one two FIFO "top-queues" (one for pre-send and one
+ * for post-send). Updates enqueued *during* doUpdate() of a "top" update go into the "sub-queue"
+ * for that update. After that method finishes, the sub-queue is run until drained. This continues
+ * for each top-queue job until the entire top queue is drained. This happens for the pre-send
+ * top-queue, and later on, the post-send top-queue, in execute().
  *
  * @since 1.19
  */
@@ -221,9 +228,10 @@ class DeferredUpdates {
        private static function runUpdate( DeferrableUpdate $update, LBFactory $lbFactory, $stage ) {
                $guiError = null;
                try {
-                       $lbFactory->beginMasterChanges( __METHOD__ );
+                       $fnameTrxOwner = get_class( $update ) . '::doUpdate';
+                       $lbFactory->beginMasterChanges( $fnameTrxOwner );
                        $update->doUpdate();
-                       $lbFactory->commitMasterChanges( __METHOD__ );
+                       $lbFactory->commitMasterChanges( $fnameTrxOwner );
                } catch ( Exception $e ) {
                        // Reporting GUI exceptions does not work post-send
                        if ( $e instanceof ErrorPageError && $stage === self::PRESEND ) {
index 30aae15..d8bc35b 100644 (file)
@@ -133,7 +133,7 @@ class SiteStatsUpdate implements DeferrableUpdate {
         */
        public static function cacheUpdate( $dbw ) {
                global $wgActiveUserDays;
-               $dbr = wfGetDB( DB_SLAVE, 'vslow' );
+               $dbr = wfGetDB( DB_REPLICA, 'vslow' );
                # Get non-bot users than did some recent action other than making accounts.
                # If account creation is included, the number gets inflated ~20+ fold on enwiki.
                $activeUsers = $dbr->selectField(
index baec396..1537535 100644 (file)
@@ -180,7 +180,7 @@ class DifferenceEngine extends ContextSource {
         */
        public function deletedLink( $id ) {
                if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $row = $dbr->selectRow( 'archive', '*',
                                [ 'ar_rev_id' => $id ],
                                __METHOD__ );
@@ -528,7 +528,7 @@ class DifferenceEngine extends ContextSource {
                        RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
                ) {
                        // Look for an unpatrolled change corresponding to this diff
-                       $db = wfGetDB( DB_SLAVE );
+                       $db = wfGetDB( DB_REPLICA );
                        $change = RecentChange::newFromConds(
                                [
                                        'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
@@ -695,10 +695,10 @@ class DifferenceEngine extends ContextSource {
        }
 
        /**
-        * Add style sheets and supporting JS for diff display.
+        * Add style sheets for diff display.
         */
        public function showDiffStyle() {
-               $this->getOutput()->addModuleStyles( 'mediawiki.action.history.diff' );
+               $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' );
        }
 
        /**
@@ -1338,7 +1338,7 @@ class DifferenceEngine extends ContextSource {
                }
 
                // Load tags information for both revisions
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                if ( $this->mOldid !== false ) {
                        $this->mOldTags = $dbr->selectField(
                                'tag_summary',
index b454577..2eae279 100644 (file)
@@ -112,7 +112,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
        }
 
        /**
-        * Get a slave database connection for the specified cluster
+        * Get a replica DB connection for the specified cluster
         *
         * @param string $cluster Cluster name
         * @return IDatabase
@@ -130,7 +130,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                        wfDebug( "writable external store\n" );
                }
 
-               $db = $lb->getConnection( DB_SLAVE, [], $wiki );
+               $db = $lb->getConnection( DB_REPLICA, [], $wiki );
                $db->clearFlag( DBO_TRX ); // sanity
 
                return $db;
@@ -264,7 +264,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
        }
 
        /**
-        * Helper function for self::batchFetchBlobs for merging master/slave results
+        * Helper function for self::batchFetchBlobs for merging master/replica DB results
         * @param array &$ret Current self::batchFetchBlobs return value
         * @param array &$ids Map from blob_id to requested itemIDs
         * @param mixed $res DB result from Database::select
index 10183f4..ed36a1f 100644 (file)
@@ -241,31 +241,31 @@ abstract class FileBackend {
         *
         * 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>,
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     );
+        *     ]
         * @endcode
         *
         * b) Copy a file system file into storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'store',
         *         'src'                 => <file system path, FSFile, or TempFSFile>,
         *         'dst'                 => <storage path>,
         *         'overwrite'           => <boolean>,
         *         'overwriteSame'       => <boolean>,
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     )
+        *     ]
         * @endcode
         *
         * c) Copy a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'copy',
         *         'src'                 => <storage path>,
         *         'dst'                 => <storage path>,
@@ -273,12 +273,12 @@ abstract class FileBackend {
         *         'overwriteSame'       => <boolean>,
         *         'ignoreMissingSource' => <boolean>, # since 1.21
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     )
+        *     ]
         * @endcode
         *
         * d) Move a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'move',
         *         'src'                 => <storage path>,
         *         'dst'                 => <storage path>,
@@ -286,32 +286,32 @@ abstract class FileBackend {
         *         'overwriteSame'       => <boolean>,
         *         'ignoreMissingSource' => <boolean>, # since 1.21
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     )
+        *     ]
         * @endcode
         *
         * e) Delete a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'delete',
         *         'src'                 => <storage path>,
         *         'ignoreMissingSource' => <boolean>
-        *     )
+        *     ]
         * @endcode
         *
         * f) Update metadata for a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'describe',
         *         'src'                 => <storage path>,
         *         'headers'             => <HTTP header name/value map>
-        *     )
+        *     ]
         * @endcode
         *
         * g) Do nothing (no-op)
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'null',
-        *     )
+        *     ]
         * @endcode
         *
         * Boolean flags for operations (operation-specific):
@@ -513,69 +513,69 @@ abstract class FileBackend {
         *
         * 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>,
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     )
+        *     ]
         * @endcode
         *
         * b) Copy a file system file into storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'store',
         *         'src'                 => <file system path, FSFile, or TempFSFile>,
         *         'dst'                 => <storage path>,
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     )
+        *     ]
         * @endcode
         *
         * c) Copy a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'copy',
         *         'src'                 => <storage path>,
         *         'dst'                 => <storage path>,
         *         'ignoreMissingSource' => <boolean>, # since 1.21
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     )
+        *     ]
         * @endcode
         *
         * d) Move a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'move',
         *         'src'                 => <storage path>,
         *         'dst'                 => <storage path>,
         *         'ignoreMissingSource' => <boolean>, # since 1.21
         *         'headers'             => <HTTP header name/value map> # since 1.21
-        *     )
+        *     ]
         * @endcode
         *
         * e) Delete a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'delete',
         *         'src'                 => <storage path>,
         *         'ignoreMissingSource' => <boolean>
-        *     )
+        *     ]
         * @endcode
         *
         * f) Update metadata for a file within storage
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'describe',
         *         'src'                 => <storage path>,
         *         'headers'             => <HTTP header name/value map>
-        *     )
+        *     ]
         * @endcode
         *
         * g) Do nothing (no-op)
         * @code
-        *     array(
+        *     [
         *         'op'                  => 'null',
-        *     )
+        *     ]
         * @endcode
         *
         * @par Boolean flags for operations (operation-specific):
@@ -1034,7 +1034,7 @@ abstract class FileBackend {
         *
         * 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.
+        * In that latter case, there are copies of the file that must stay in sync.
         * Additionally, further calls to this function may return the same file.
         *
         * @param array $params Parameters include:
index a29119c..bc4d81d 100644 (file)
@@ -1443,7 +1443,7 @@ abstract class FileBackendStore extends FileBackend {
        /**
         * Like resolveStoragePath() except null values are returned if
         * the container is sharded and the shard could not be determined
-        * or if the path ends with '/'. The later case is illegal for FS
+        * or if the path ends with '/'. The latter case is illegal for FS
         * backends and can confuse listings for object store backends.
         *
         * This function is used when resolving paths that must be valid
@@ -1702,8 +1702,8 @@ abstract class FileBackendStore extends FileBackend {
                if ( $path === null ) {
                        return; // invalid storage path
                }
-               $age = time() - wfTimestamp( TS_UNIX, $val['mtime'] );
-               $ttl = min( 7 * 86400, max( 300, floor( .1 * $age ) ) );
+               $mtime = wfTimestamp( TS_UNIX, $val['mtime'] );
+               $ttl = $this->memCache->adaptiveTTL( $mtime, 7 * 86400, 300, .1 );
                $key = $this->fileCacheKey( $path );
                // Set the cache unless it is currently salted.
                $this->memCache->set( $key, $val, $ttl );
index 567a298..a3cb3b1 100644 (file)
@@ -103,17 +103,17 @@ abstract class LockManager {
         */
        final public function lockByType( array $pathsByType, $timeout = 0 ) {
                $pathsByType = $this->normalizePathsByType( $pathsByType );
-               $msleep = [ 0, 50, 100, 300, 500 ]; // retry backoff times
-               $start = microtime( true );
-               do {
-                       $status = $this->doLockByType( $pathsByType );
-                       $elapsed = microtime( true ) - $start;
-                       if ( $status->isOK() || $elapsed >= $timeout || $elapsed < 0 ) {
-                               break; // success, timeout, or clock set back
-                       }
-                       usleep( 1e3 * ( next( $msleep ) ?: 1000 ) ); // use 1 sec after enough times
-                       $elapsed = microtime( true ) - $start;
-               } while ( $elapsed < $timeout && $elapsed >= 0 );
+
+               $status = null;
+               $loop = new WaitConditionLoop(
+                       function () use ( &$status, $pathsByType ) {
+                               $status = $this->doLockByType( $pathsByType );
+
+                               return $status->isOK() ?: WaitConditionLoop::CONDITION_CONTINUE;
+                       },
+                       $timeout
+               );
+               $loop->invoke();
 
                return $status;
        }
index cb5266a..2f17e27 100644 (file)
@@ -276,6 +276,7 @@ class MemcLockManager extends QuorumLockManager {
         * @return MemcachedBagOStuff|null
         */
        protected function getCache( $lockSrv ) {
+               /** @var BagOStuff $memc */
                $memc = null;
                if ( isset( $this->bagOStuffs[$lockSrv] ) ) {
                        $memc = $this->bagOStuffs[$lockSrv];
@@ -337,20 +338,21 @@ class MemcLockManager extends QuorumLockManager {
                // 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
+               $loop = new WaitConditionLoop(
+                       function () use ( $memc, $keys, &$lockedKeys ) {
+                               foreach ( array_diff( $keys, $lockedKeys ) as $key ) {
+                                       if ( $memc->add( "$key:mutex", 1, 180 ) ) { // lock record
+                                               $lockedKeys[] = $key;
+                                       }
                                }
-                       }
-               } while ( count( $lockedKeys ) < count( $keys ) && ( microtime( true ) - $start ) <= 3 );
+
+                               return array_diff( $keys, $lockedKeys )
+                                       ? WaitConditionLoop::CONDITION_CONTINUE
+                                       : true;
+                       },
+                       3.0 // timeout
+               );
+               $loop->invoke();
 
                if ( count( $lockedKeys ) != count( $keys ) ) {
                        $this->releaseMutexes( $memc, $lockedKeys ); // failed; release what was locked
index aec337e..596dbde 100644 (file)
@@ -27,7 +27,7 @@
  * @brief Proxy backend that manages file layout rewriting for FileRepo.
  *
  * LocalRepo may be configured to store files under their title names or by SHA-1.
- * This acts as a shim in the later case, providing backwards compatability for
+ * This acts as a shim in the latter case, providing backwards compatability for
  * most callers. All "public"/"deleted" zone files actually go in an "original"
  * container and are never changed.
  *
@@ -94,7 +94,7 @@ class FileBackendDBRepoWrapper extends FileBackend {
         * @return array Translated paths in same order
         */
        public function getBackendPaths( array $paths, $latest = true ) {
-               $db = $this->getDB( $latest ? DB_MASTER : DB_SLAVE );
+               $db = $this->getDB( $latest ? DB_MASTER : DB_REPLICA );
 
                // @TODO: batching
                $resolved = [];
index 4ab913d..b8b1cf6 100644 (file)
@@ -482,8 +482,8 @@ class FileRepo {
         * @param array $items An array of titles, or an array of findFile() options with
         *    the "title" option giving the title. Example:
         *
-        *     $findItem = array( 'title' => $title, 'private' => true );
-        *     $findBatch = array( $findItem );
+        *     $findItem = [ 'title' => $title, 'private' => true ];
+        *     $findBatch = [ $findItem ];
         *     $repo->findFiles( $findBatch );
         *
         *    No title should appear in $items twice, as the result use titles as keys
index 8619ba6..645a59b 100644 (file)
@@ -28,13 +28,13 @@ use MediaWiki\Logger\LoggerFactory;
  *
  * Example config:
  *
- * $wgForeignFileRepos[] = array(
+ * $wgForeignFileRepos[] = [
  *   'class'                  => 'ForeignAPIRepo',
  *   'name'                   => 'shared',
  *   'apibase'                => 'https://en.wikipedia.org/w/api.php',
  *   'fetchDescription'       => true, // Optional
  *   'descriptionCacheExpiry' => 3600,
- * );
+ * ];
  *
  * @ingroup FileRepo
  */
index a59ca34..f8b1ed9 100644 (file)
@@ -63,7 +63,7 @@ class ForeignDBViaLBRepo extends LocalRepo {
         * @return IDatabase
         */
        function getSlaveDB() {
-               return wfGetDB( DB_SLAVE, [], $this->wiki );
+               return wfGetDB( DB_REPLICA, [], $this->wiki );
        }
 
        /**
index eaec151..fccb755 100644 (file)
@@ -453,11 +453,11 @@ class LocalRepo extends FileRepo {
        }
 
        /**
-        * Get a connection to the slave DB
+        * Get a connection to the replica DB
         * @return DatabaseBase
         */
        function getSlaveDB() {
-               return wfGetDB( DB_SLAVE );
+               return wfGetDB( DB_REPLICA );
        }
 
        /**
@@ -469,7 +469,7 @@ class LocalRepo extends FileRepo {
        }
 
        /**
-        * Get a callback to get a DB handle given an index (DB_SLAVE/DB_MASTER)
+        * Get a callback to get a DB handle given an index (DB_REPLICA/DB_MASTER)
         * @return Closure
         */
        protected function getDBFactory() {
index ca1ea84..d1e683a 100644 (file)
@@ -177,7 +177,7 @@ class ArchivedFile {
 
                if ( !$this->title || $this->title->getNamespace() == NS_FILE ) {
                        $this->dataLoaded = true; // set it here, to have also true on miss
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $row = $dbr->selectRow(
                                'filearchive',
                                self::selectFields(),
index de3cdbe..d63a91b 100644 (file)
@@ -431,16 +431,18 @@ class LocalFile extends File {
        private function loadFieldsWithTimestamp( $dbr, $fname ) {
                $fieldMap = false;
 
-               $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
-                       [ 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ],
-                       $fname );
+               $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), [
+                               'img_name' => $this->getName(),
+                               'img_timestamp' => $dbr->timestamp( $this->getTimestamp() )
+                       ], $fname );
                if ( $row ) {
                        $fieldMap = $this->unprefixRow( $row, 'img_' );
                } else {
                        # File may have been uploaded over in the meantime; check the old versions
-                       $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
-                               [ 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ],
-                               $fname );
+                       $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), [
+                                       'oi_name' => $this->getName(),
+                                       'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() )
+                               ], $fname );
                        if ( $row ) {
                                $fieldMap = $this->unprefixRow( $row, 'oi_' );
                        }
index 406667e..d1a9bc5 100644 (file)
@@ -377,7 +377,7 @@ class WikiImporter {
                // Update article count statistics (T42009)
                // The normal counting logic in WikiPage->doEditUpdates() is designed for
                // one-revision-at-a-time editing, not bulk imports. In this situation it
-               // suffers from issues of slave lag. We let WikiPage handle the total page
+               // suffers from issues of replica DB lag. We let WikiPage handle the total page
                // and revision count, and we implement our own custom logic for the
                // article (content page) count.
                $page = WikiPage::factory( $title );
index ea96cfe..cd9574c 100644 (file)
@@ -3,7 +3,8 @@
                "authors": [
                        "Xuacu",
                        "Fitoschido",
-                       "Enolp"
+                       "Enolp",
+                       "Crucifunked"
                ]
        },
        "config-desc": "L'instalador pa MediaWiki",
        "config-outdated-sqlite": "'''Avisu:''' tien SQLite $1, que ye inferior a la versión mínima necesaria $2. SQLite nun tará disponible.",
        "config-no-fts3": "'''Avisu:''' SQLite ta compiláu ensin el [//sqlite.org/fts3.html módulu FTS3]; les funciones de gueta nun tarán disponibles nesti sistema.",
        "config-pcre-old": "<strong>Fatal:</strong> Ríquese PCRE $1 o posterior.\nEl binariu de PHP ta enllazáu con PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Más información].",
+       "config-pcre-no-utf8": "<strong>Erru fatal:</strong> Paez que'l módulu PCRE de PHP foi compiláu ensin el soporte PCRE_UTF8.\nMediaWiki requier compatibilidá con UTF_8 pa furrular correutamente.",
+       "config-memory-raised": "El parámetru <code>memory_limit</code> de PHP ye $1. Auméntase a $2.",
+       "config-memory-bad": "<strong>Alvertencia:</strong>: el parámetru <code>memory_limit</code> de PHP ye $1.\nProbablemente sía demasiáu baxu.\n¡La instalación puede fallar!",
+       "config-xcache": "[http://xcache.lighttpd.net/ XCache] ta instaláu",
        "config-apc": "[http://www.php.net/apc APC] ta instaláu",
        "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] ta instaláu",
+       "config-no-cache-apcu": "<strong>Warning:</strong> Non pudo atopase[http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nEl caxé d'oxetos nun ta activáu.",
+       "config-mod-security": "<strong>Alvertencia:</strong> El to servidor web tien activáu [http://modsecurity.org/mod_security]/mod_security2 .Munches de les sos configuraciones comunes pueden causar problemes a MediaWiki o otru software que dexe a los usuarios publicar conteníu arbitrario. De ser posible, tendríes de desactivalo. Si non, consulta la  [http://modsecurity.org/documentation/ mod_security documentation] o contacta col alministrador del to servidor si atopes erros aleatorios.",
        "config-diff3-bad": "Nun s'alcontró GNU diff3.",
        "config-git": "Alcontróse'l software de control de versiones Git: <code>$1</code>.",
        "config-git-bad": "Nun s'alcontró el software de control de versiones Git.",
+       "config-imagemagick": "ImageMagick atopáu: <code>$1</code>.\nLa miniaturización d'imaxes habilitaráse si habilites les cargues.",
+       "config-gd": "Atopóse una biblioteca de gráficos GD integrada.\nLa miniaturización d'imaxes habilitaráse si habilites les xubíes.",
+       "config-no-scaling": "Nun s'atopó la biblioteca GD o ImageMagik.\nVa desactivase la miniaturización d'imaxes.",
+       "config-no-uri": "<strong>Erru:</strong> non pudo determinase el URI actual. Atayóse la instalación.",
+       "config-no-cli-uri": "<strong>Alvertencia:</strong> Nun s'especificó  <code>--scriptpath</code>, úsase'l valor predetermináu <code>$1</code>.",
+       "config-using-server": "Utilizando'l nome de servidor \"<nowiki>$1</nowiki>\".",
+       "config-using-uri": "Utilizando la URL del servidor \"<nowiki>$1$2</nowiki>\".",
+       "config-uploads-not-safe": "<strong>Alvertencia:</strong> El to directoriu predetermináu pa les cargues <code>$1</code> ye vulnerable a la execución de scripts arbitrarios.\nAnque MediaWiki comprueba tolos archivos cargaos por si hubiera amenaces de seguridá, ye altamente recomendable [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security close this security vulnerability] enantes d'activar les cargues.",
+       "config-no-cli-uploads-check": "<strong>Alvertencia:</strong> el to directoriu predetermináu pa cargues <code>$1</code> nun tá comprobáu contra la vulnerabilidá d'execución arbitraria de scripts mientres la instalación per llínea de comandos.",
+       "config-brokenlibxml": "El sistema tien una combinación de versiones de PHP y de libxml2 que ye pocu confiable y puede provocar corrupción oculta nos datos de MediaWiki y otres aplicaciones web. Actualiza a libxml2 2.7.3 o posterior ([https://bugs.php.net/bug.php?díi=45996 bug reportáu con PHP]). Instalación albortada.",
+       "config-suhosin-max-value-length": "Suhosin ta instaláu y llinda el parámetru <code>length</code> GET a $1 bytes.\nEl componente ResourceLoader (xestor de recursos) de MediaWiki va trabayar nesta llende, pero eso va perxudicar el rendimientu.\nSi ye posible, tendríes d'establecer <code>suhosin.get.max_value_length</code> nel valor 1024 o superior en <code>php.ini</code> y establecer <code>$wgResourceLoaderMaxQueryLength</code> nel mesmu valor en <code>LocalSettings.php</code>.",
        "config-db-type": "Tipu de base de datos:",
+       "config-db-host": "Servidor de la base de datos:",
+       "config-db-host-help": "Si'l to servidor de base de datos ta n'otru servidor, escribe'l nome del equipu o la so dirección IP equí.\n\nSi tas utilizando alojamiento web compartíu, el to provisor tendría de date'l nome correctu del servidor na so documentación.\n\nSi vas instalar nun servidor Windows y a utilizar MySQL, l'usu de \"localhost\" como nome del servidor puede nun #funcionar. Si ye asina, intenta poner \"127.0.0.1\" como dirección IP local.\n\nSi utilices PostgreSQL, dexa esti campu vacío pa conectase al traviés d'un socket de Unix.",
+       "config-db-host-oracle": "TNS de la base de datos:",
+       "config-db-host-oracle-help": "Escribe un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nome de conexón local] válidu; un archivu tnsnames.ora ten de ser visible pa esta instalación.<br />Si tas utilizando biblioteques de veceru 10g o más recién tamién puedes utilizar el métodu de asignación de nomes [http://download.oracle.com/docs/cd/Y11882_01/network.112/y10836/naming.htm Easy Connect].",
+       "config-db-wiki-settings": "Identifica esta wiki",
        "config-db-name": "Nome de base de datos:",
+       "config-db-name-help": "Escueye un nome qu'identifique la to wiki. Nun tien de contener espacios. \nSi tas utilizando agospiamientu web compartíu, el to provisor va date un nome específicu de base de datos por que lu utilices, o bien va dexate crear bases de datos al traviés d'un panel de control.",
+       "config-db-name-oracle": "Esquema de la base de datos:",
        "config-db-install-account": "Cuenta d'usuariu pa la instalación",
        "config-db-username": "Nome d'usuariu de base de datos:",
        "config-db-password": "Contraseña de base de datos:",
index b55b3c7..e704141 100644 (file)
        "config-mysql-myisam": "MyISAM",
        "config-mysql-myisam-dep": "<strong>Warnung:</strong> Es wurde MyISAM als Speichersubsystem für das Datenbanksystem MySQL ausgewählt. Aus folgenden Gründen wird es nicht für den Einsatz mit MediaWiki empfohlen:\n* Es unterstützt aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen.\n* Es ist anfälliger für Datenprobleme.\n* Es wird von MediaWiki nicht immer adäquat unterstützt.\n\nSofern die vorhandene MySQL-Installation das Speichersubsystem InnoDB unterstützt, wird deren Verwendung eindringlich empfohlen.\nSofern sie es nicht unterstützt, sollte nunmehr eine entsprechende Aktualisierung in Erwägung gezogen werden.",
        "config-mysql-only-myisam-dep": "<strong>Warnung:</strong> MyISAM ist das einzige verfügbare Speichersubsystem für das Datenbanksystem MySQL auf diesem Server. Es wird nicht für die Verwendung mit MediaWiki empfohlen, da es\n* aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen unterstützt,\n* anfälliger für Datenprobleme ist und\n* von MediaWiki nicht immer adäquat unterstützt wird.\n\nDeine MySQL-Installation unterstützt nicht das Speichersubsystem InnoDB. Eine Aktualisierung wird nunmehr empfohlen.",
-       "config-mysql-engine-help": "'''InnoDB''' ist fast immer die bessere Wahl, da es gleichzeitige Zugriffe gut unterstützt.\n\n'''MyISAM''' ist in Einzelnutzerumgebungen sowie bei schreibgeschützten Wikis schneller.\nBei MyISAM-Datenbanken treten tendenziell häufiger Fehler auf als bei InnoDB-Datenbanken.",
+       "config-mysql-engine-help": "<strong>InnoDB</strong> als Speichersubsystem für das Datenbanksystem MySQL ist fast immer die bessere Wahl, da es gleichzeitige Zugriffe gut unterstützt.\n\n<strong>MyISAM</strong> als Speichersubsystem für das Datenbanksystem MySQL ist hingegen in Einzelnutzerumgebungen oder bei schreibgeschützten Wikis schneller.\nDatenbanken, die MyISAM verwenden, sind indes tendenziell fehleranfälliger als solche, die InnoDB verwenden.",
        "config-mysql-charset": "Datenbankzeichensatz:",
        "config-mysql-binary": "binär",
        "config-mysql-utf8": "UTF-8",
index 1d230b2..3e7f8f3 100644 (file)
        "config-type-sqlite": "SQLite",
        "config-type-oracle": "Oracle",
        "config-type-mssql": "Microsoft SQL Server",
-       "config-support-info": "MediaWiki supporte ces systèmes de bases de données :\n\n$1\n\nSi vous ne voyez pas le système de base de données que vous essayez d'utiliser ci-dessous, alors suivez les instructions ci-dessus (voir liens) pour activer le support.",
+       "config-support-info": "MediaWiki prend en charge ces systèmes de bases de données :\n\n$1\n\nSi vous ne voyez pas le système de base de données que vous essayez d’utiliser ci-dessous, alors suivez les instructions ci-dessus (voir liens) pour activer la prise en charge.",
        "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] est le premier choix pour MediaWiki et est le mieux pris en charge. MediaWiki fonctionne aussi avec [{{int:version-db-mariadb-url}} MariaDB] et [{{int:version-db-percona-url}} Percona Server], qui sont compatibles avec MySQL. ([http://www.php.net/manual/en/mysqli.installation.php Comment compiler PHP avec le support MySQL])",
        "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] est un système de base de données populaire et ''open source'' qui peut être une alternative à MySQL. Son support peut contenir quelques bogues mineurs et n'est pas recommandé dans un environnement de production.  ([http://www.php.net/manual/en/pgsql.installation.php Comment compiler PHP avec le support de PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] est un système de base de données léger bien supporté. ([http://www.php.net/manual/en/pdo.installation.php Comment compiler PHP avec le support de SQLite], en utilisant PDO)",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] est un système de base de données léger bien pris en charge. ([http://www.php.net/manual/fr/pdo.installation.php Comment compiler PHP avec la prise en charge de SQLite], en utilisant PDO)",
        "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] est un système commercial de gestion de base de données d’entreprise. ([http://www.php.net/manual/en/oci8.installation.php Comment compiler PHP avec le support OCI8])",
        "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] est une base de données commerciale d’entreprise pour Windows. ([http://www.php.net/manual/en/sqlsrv.installation.php Comment compiler PHP avec le support de SQLSRV])",
        "config-header-mysql": "Paramètres de MySQL",
        "config-mysql-engine": "Moteur de stockage :",
        "config-mysql-innodb": "InnoDB",
        "config-mysql-myisam": "MyISAM",
-       "config-mysql-myisam-dep": "''' Avertissement ''': vous avez sélectionné MyISAM comme moteur de stockage pour MySQL, ce qui n'est pas recommandé pour une utilisation avec MediaWiki, parce que:\n * il supporte à peine la simultanéité en raison de verrouillage de table\n * il est plus sujet à la corruption que les autres moteurs\n * le codebase MediaWiki ne gère pas toujours MyISAM comme il se doit\n\nSi votre installation MySQL supporte InnoDB, il est fortement recommandé que vous le choisissiez plutôt. \nSi votre installation MySQL ne supporte pas les tables InnoDB, il est peut-être temps de faire une mise à niveau.",
-       "config-mysql-only-myisam-dep": "'''Attention :''' MyISAM est le seul moteur de stockage disponible pour MySQL sur cette machine, et cela n’est pas recommandé pour une utilisation avec MédiaWiki, car :\n* il supporte très peu les accès concurrents à cause du verrouillage des tables\n* il est plus sujet à corruption que les autres moteurs\n* le code de base de MédiaWiki ne gère pas toujours MyISAM comme il faudrait\n\nVotre installation MySQL ne supporte pas InnoDB ; il est peut-être temps de la mettre à jour.",
-       "config-mysql-engine-help": "'''InnoDB''' est presque toujours la meilleure option, car il supporte bien les accès concurrents.\n\n'''MyISAM''' peut être plus rapide dans les installations monoposte ou en lecture seule. \nLes bases de données MyISAM ont tendance à se corrompre plus souvent que les bases d'InnoDB.",
+       "config-mysql-myisam-dep": "''' Avertissement ''': vous avez sélectionné MyISAM comme moteur de stockage pour MySQL, ce qui n’est pas recommandé pour une utilisation avec MediaWiki, parce que :\n * il prend à peine en charge la simultanéité en raison de verrouillage de table\n * il est plus sujet à la corruption que les autres moteurs\n * le code de base MediaWiki ne gère pas toujours MyISAM comme il se doit\n\nSi votre installation MySQL prenden charge InnoDB, il est fortement recommandé que vous le choisissiez plutôt. \nSi votre installation MySQL ne prend pas en charge les tables InnoDB, il est peut-être temps de faire une mise à niveau.",
+       "config-mysql-only-myisam-dep": "'''Attention :''' MyISAM est le seul moteur de stockage disponible pour MySQL sur cette machine, et cela n’est pas recommandé pour une utilisation avec MédiaWiki, car :\n* il prend très peu en charge les accès concurrents à cause du verrouillage des tables\n* il est plus sujet à corruption que les autres moteurs\n* le code de base de MédiaWiki ne gère pas toujours MyISAM comme il faudrait\n\nVotre installation MySQL ne prend pas en charge InnoDB ; il est peut-être temps de la mettre à jour.",
+       "config-mysql-engine-help": "'''InnoDB''' est presque toujours la meilleure option, car il prend bien en charge les accès concurrents.\n\n'''MyISAM''' peut être plus rapide dans les installations monoposte ou en lecture seule.\nLes bases de données MyISAM ont tendance à se corrompre plus souvent que les bases d’InnoDB.",
        "config-mysql-charset": "Jeu de caractères de la base de données :",
        "config-mysql-binary": "Binaire",
        "config-mysql-utf8": "UTF-8",
        "config-help": "aide",
        "config-help-tooltip": "cliquer pour agrandir",
        "config-nofile": "Le fichier « $1 » est introuvable. A-t-il été supprimé ?",
-       "config-extension-link": "Saviez-vous que votre wiki supporte [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions des extensions] ?\n\nVous pouvez consulter les [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions par catégorie] ou la [https://www.mediawiki.org/wiki/Extension_Matrix matrice des extensions] pour voir la liste complète des extensions.",
+       "config-extension-link": "Saviez-vous que votre wiki prend en charge [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions des extensions] ?\n\nVous pouvez consulter les [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions par catégorie] ou la [https://www.mediawiki.org/wiki/Extension_Matrix matrice des extensions] pour voir la liste complète des extensions.",
        "mainpagetext": "<strong>MediaWiki a été installé.</strong>",
        "mainpagedocfooter": "Consultez le [https://meta.wikimedia.org/wiki/Help:Contents/fr Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel de wiki.\n\n== Pour démarrer ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Liste des paramètres de configuration]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/fr Questions courantes sur MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Adaptez MediaWiki dans votre langue]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Apprendre comment lutter contre le pourriel dans votre wiki]"
 }
diff --git a/includes/installer/i18n/lij.json b/includes/installer/i18n/lij.json
new file mode 100644 (file)
index 0000000..54c2c9e
--- /dev/null
@@ -0,0 +1,24 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Giromin Cangiaxo"
+               ]
+       },
+       "config-desc": "Programma de installaçion pe MediaWiki",
+       "config-title": "Installaçion de MediaWiki $1",
+       "config-information": "Informaçioin",
+       "config-localsettings-upgrade": "L'è stæto rilevou un file <code>LocalSettings.php</code>.\nPe aggiornâ questa installaçion, se prega de insei o valô de <code>$wgUpgradeKey</code> inta casella chì de sotta.\nO poei atrovâ in <code>LocalSettings.php</code>.",
+       "config-localsettings-cli-upgrade": "L 'è stæto rilevou un file <code>LocalSettings.php</code>.\nPe aggiornâ questa installaçion, eseguî <code>update.php</code>",
+       "config-localsettings-key": "Ciave de aggiornamento:",
+       "config-localsettings-badkey": "A ciave d'agiornamento che t'hæ fornio a no l'è corretta.",
+       "config-upgrade-key-missing": "L'è stæto rilevou un'installaçion existente de MediaWiki.\nPe aggiornâ quest'installaçion, se prega de insei a seguente riga inta parte infeiô do to <code>LocalSettings.php</code>:\n\n$1",
+       "config-localsettings-incomplete": "O file <code>LocalSettings.php</code> existente pâ ese incompleto.\nA variabile $1 a no l'è impostâ.\nCangia <code>LocalSettings.php</code> de moddo che questa variabile a segge impostâ e fanni clic insce \"{{int:Config-continue}}\".",
+       "config-localsettings-connection-error": "S'è veificou un errô durante a connescion a-o database doeuviando e impostaçioin specificæ in <code>LocalSettings.php</code>. Se prega de coreze queste impostaçioin e riprovâ.\n\n$1",
+       "config-session-error": "Errô inte l'avvio da sescion: $1",
+       "config-session-expired": "I dæti da sescion pan ese descheiti.\nE sescioin son configuæ pe 'na duata de $1.\nTi poeu aomentâla impostando <code>session.gc_maxlifetime</code> into file php.ini.\nRiavvia o processo d'installaçion.",
+       "config-no-session": "I dæti da sescion son andæti persci!\nControlla o to file php.ini e aseguite che <code>session.save_path</code> o l'è impostou insce 'na directory apropiâ.",
+       "config-your-language": "A to lengua:",
+       "config-your-language-help": "Seleçion-a una lengua da doeuviâ  durante o processo d'installaçion.",
+       "config-wiki-language": "A lengua do wiki:",
+       "config-wiki-language-help": "Seleçion-a a lengua ch'a saiâ prevalentemente doeuviâ into wiki."
+}
index 914618b..6ef4dd3 100644 (file)
        "config-continue": "ಮುಂದುವರೆಸಾಲೆ →",
        "config-page-language": "ಬಾಸೆ",
        "config-page-welcome": "ಮಾಧ್ಯಮವಿಕಿಗ್ ಸ್ವಾಗತ",
-       "config-page-dbconnect": "ದತà³\8dತಾà²\82ಶಸà²\82à²\9aಯà²\97à³\8d à²¸à²\82ಪರà³\8dà²\95ಕೊರ್ಲೆ",
+       "config-page-dbconnect": "ದತà³\8dತಾà²\82ಸà³\8a à²¸à²\82à²\9aಯà³\8aà²\97à³\8d à²\95à³\8aಲಿà²\95à³\86ಕೊರ್ಲೆ",
        "config-page-name": "ಪುದರ್",
        "config-page-options": "ಆಯ್ಕೆಲು",
        "config-page-install": "ಸ್ಥಾಪಿಸಾಲೆ",
-       "config-page-complete": "ಪà³\82ರà³\8dಣ!",
-       "config-page-readme": "à²\8eನನà³\8d à²\93ದà³\81ಲà³\87",
-       "config-page-releasenotes": "ಬà³\81ಡà³\81à²\97à³\8aಡà³\86ದ à²\9fಿಪà³\8dಪಣಿಲà³\81",
+       "config-page-complete": "ಮà³\81à²\97ಿà²\82ಡà³\8d!",
+       "config-page-readme": "à²\8eನನà³\8d à²\93ದà³\81ಲà³\86",
+       "config-page-releasenotes": "ಬುಡುಗಡೆದ ಟಿಪ್ಪಣಿಲು",
        "config-page-copying": "ನಕಲ್ ಮಲ್ತೊಂದುಂಡು",
        "config-page-upgradedoc": "ಪರಿಷ್ಕರಣೆ ಆವೊಂದುಂಡು",
-       "config-page-existingwiki": "ಪà³\8dರಸà³\8dತà³\81ತ ವಿಕಿ",
-       "config-restart": "ಸರಿ,à²\95à³\81ಡ à²ªà³\8dರಾರà²\82ಭ ಮಲ್ಪುಲೆ",
+       "config-page-existingwiki": "à²\87ತà³\8dತà³\86ದ ವಿಕಿ",
+       "config-restart": "ಸರಿ,à²\95à³\81ಡ à²¸à³\81ರà³\81 ಮಲ್ಪುಲೆ",
        "config-db-type": "ದತ್ತಾಂಶಸಂಚಯ ಮಾದರಿ:",
        "config-db-host-oracle": "ದತ್ತಾಂಶಸಂಚಯ TNS:",
        "config-db-wiki-settings": "ಈ ವಿಕಿಯನ್ನು ಗುರುತಿಸಾಲೆ",
index 08aa8c1..365231c 100644 (file)
        "config-subscribe-help": "thông báo an ninh.\nBạn nên đồng ý với nó và cập nhật bản cài đặt MediaWiki của bạn khi phiên bản mới xuất hiện.",
        "config-subscribe-noemail": "Bạn đã cố gắng để đăng ký vào danh sách thư thông báo phát hành mà không cung cấp một địa chỉ thư điện tử nào cả.\nVui lòng cung cấp một địa chỉ thư điện tử nếu bạn muốn đăng ký vào danh sách thư.",
        "config-pingback": "Chia sẻ dữ liệu về bản cài đặt này với nhóm phát triển MediaWiki.",
+       "config-pingback-help": "Nếu bạn kích hoạt chức năng này, MediaWiki sẽ thỉnh thoảng gửi cho https://www.mediawiki.org/ dữ liệu cơ bản về bản cài đặt MediaWiki này. Dữ liệu này có chẳng hạn loại máy, phiên bản PHP, và phía sau cơ sở dữ liệu đã chọn. Quỹ Wikimedia chia sẻ dữ liệu này với các nhà phát triển MediaWiki để giúp họ trong việc phát triển phần mềm vào tương lai. Dữ liệu sau sẽ được gửi từ hệ thống này:\n<pre>$1</pre>",
        "config-almost-done": "Bạn gần như đã hoàn tất!\nBây giờ bạn có thể bỏ qua cấu hình còn lại và cài đặt wiki ngay bây giờ.",
        "config-optional-continue": "Hỏi tôi về thêm chi tiết.",
        "config-optional-skip": "Chán quá, cài đặt wiki rỗi.",
index 6ac165a..0e107b3 100644 (file)
@@ -37,7 +37,7 @@ use WANObjectCache;
  * and tree storage backends (SQL, CDB, and plain PHP arrays).
  *
  * All information is loaded on creation when called by $this->fetch( $prefix ).
- * All work is done on slave, because this should *never* change (except during
+ * All work is done on replica DB, because this should *never* change (except during
  * schema updates etc, which aren't wiki-related)
  *
  * @since 1.28
@@ -282,7 +282,7 @@ class ClassicInterwikiLookup implements InterwikiLookup {
                        $this->objectCache->makeKey( 'interwiki', $prefix ),
                        $this->objectCacheExpiry,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) {
-                               $dbr = wfGetDB( DB_SLAVE ); // TODO: inject LoadBalancer
+                               $dbr = wfGetDB( DB_REPLICA ); // TODO: inject LoadBalancer
 
                                $setOpts += Database::getCacheSetOptions( $dbr );
 
@@ -395,7 +395,7 @@ class ClassicInterwikiLookup implements InterwikiLookup {
         * @return array[] Interwiki rows
         */
        private function getAllPrefixesDB( $local ) {
-               $db = wfGetDB( DB_SLAVE ); // TODO: inject DB LoadBalancer
+               $db = wfGetDB( DB_REPLICA ); // TODO: inject DB LoadBalancer
 
                $where = [];
 
index 459910a..d0a7719 100644 (file)
@@ -26,7 +26,7 @@ use Interwiki;
 /**
  * Service interface for looking up Interwiki records.
  *
- * @singe 1.28
+ * @since 1.28
  */
 interface InterwikiLookup {
 
index d64be3c..020a684 100644 (file)
@@ -553,7 +553,7 @@ abstract class JobQueue {
        }
 
        /**
-        * Wait for any slaves or backup servers to catch up.
+        * Wait for any replica DBs or backup servers to catch up.
         *
         * This does nothing for certain queue classes.
         *
index 3a1da13..f6b4d53 100644 (file)
@@ -236,7 +236,7 @@ class JobQueueDB extends JobQueue {
                        }
                        // Build the full list of job rows to insert
                        $rows = array_merge( $rowList, array_values( $rowSet ) );
-                       // Insert the job rows in chunks to avoid slave lag...
+                       // Insert the job rows in chunks to avoid replica DB lag...
                        foreach ( array_chunk( $rows, 50 ) as $rowBatch ) {
                                $dbw->insert( 'job', $rowBatch, $method );
                        }
@@ -732,7 +732,7 @@ class JobQueueDB extends JobQueue {
         */
        protected function getSlaveDB() {
                try {
-                       return $this->getDB( DB_SLAVE );
+                       return $this->getDB( DB_REPLICA );
                } catch ( DBConnectionError $e ) {
                        throw new JobQueueConnectionError( "DBConnectionError:" . $e->getMessage() );
                }
@@ -751,7 +751,7 @@ class JobQueueDB extends JobQueue {
        }
 
        /**
-        * @param int $index (DB_SLAVE/DB_MASTER)
+        * @param int $index (DB_REPLICA/DB_MASTER)
         * @return DBConnRef
         */
        protected function getDB( $index ) {
index a4b3241..8d57562 100644 (file)
@@ -255,7 +255,7 @@ class JobQueueGroup {
        }
 
        /**
-        * Wait for any slaves or backup queue servers to catch up.
+        * Wait for any replica DBs or backup queue servers to catch up.
         *
         * This does nothing for certain queue classes.
         *
index f25c1ba..570d6db 100644 (file)
@@ -23,6 +23,7 @@
 
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Logger\LoggerFactory;
+use Liuggio\StatsdClient\Factory\StatsdDataFactory;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerInterface;
 
@@ -42,7 +43,7 @@ class JobRunner implements LoggerAwareInterface {
        protected $logger;
 
        const MAX_ALLOWED_LAG = 3; // abort if more than this much DB lag is present
-       const LAG_CHECK_PERIOD = 1.0; // check slave lag this many seconds
+       const LAG_CHECK_PERIOD = 1.0; // check replica DB lag this many seconds
        const ERROR_BACKOFF_TTL = 1; // seconds to back off a queue due to errors
 
        /**
@@ -114,19 +115,20 @@ class JobRunner implements LoggerAwareInterface {
                        $response['reached'] = 'read-only';
                        return $response;
                }
+
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                // Bail out if there is too much DB lag.
                // This check should not block as we want to try other wiki queues.
-               list( , $maxLag ) = wfGetLB( wfWikiID() )->getMaxLag();
+               list( , $maxLag ) = $lbFactory->getMainLB( wfWikiID() )->getMaxLag();
                if ( $maxLag >= self::MAX_ALLOWED_LAG ) {
-                       $response['reached'] = 'slave-lag-limit';
+                       $response['reached'] = 'replica-lag-limit';
                        return $response;
                }
 
                // Flush any pending DB writes for sanity
-               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $lbFactory->commitAll( __METHOD__ );
 
-               // Catch huge single updates that lead to slave lag
+               // Catch huge single updates that lead to replica DB lag
                $trxProfiler = Profiler::instance()->getTransactionProfiler();
                $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
                $trxProfiler->setExpectations( $wgTrxProfilerLimits['JobRunner'], __METHOD__ );
@@ -137,11 +139,11 @@ class JobRunner implements LoggerAwareInterface {
                $wait = 'wait'; // block to read backoffs the first time
 
                $group = JobQueueGroup::singleton();
-               $stats = RequestContext::getMain()->getStats();
+               $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
                $jobsPopped = 0;
                $timeMsTotal = 0;
                $startTime = microtime( true ); // time since jobs started running
-               $lastCheckTime = 1; // timestamp of last slave check
+               $lastCheckTime = 1; // timestamp of last replica DB check
                do {
                        // Sync the persistent backoffs with concurrent runners
                        $backoffs = $this->syncBackoffDeltas( $backoffs, $backoffDeltas, $wait );
@@ -159,6 +161,7 @@ class JobRunner implements LoggerAwareInterface {
                        } else {
                                $job = $group->pop( $type ); // job from a single queue
                        }
+                       $lbFactory->commitMasterChanges( __METHOD__ ); // flush any JobQueueDB writes
 
                        if ( $job ) { // found a job
                                ++$jobsPopped;
@@ -178,8 +181,7 @@ class JobRunner implements LoggerAwareInterface {
                                        $backoffs = $this->syncBackoffDeltas( $backoffs, $backoffDeltas, $wait );
                                }
 
-                               $lbFactory->commitMasterChanges( __METHOD__ ); // flush any JobQueueDB writes
-                               $info = $this->executeJob( $job, $stats, $popTime );
+                               $info = $this->executeJob( $job, $lbFactory, $stats, $popTime );
                                if ( $info['status'] !== false || !$job->allowRetries() ) {
                                        $group->ack( $job ); // succeeded or job cannot be retried
                                        $lbFactory->commitMasterChanges( __METHOD__ ); // flush any JobQueueDB writes
@@ -210,7 +212,7 @@ class JobRunner implements LoggerAwareInterface {
                                        break;
                                }
 
-                               // Don't let any of the main DB slaves get backed up.
+                               // Don't let any of the main DB replica DBs get backed up.
                                // This only waits for so long before exiting and letting
                                // other wikis in the farm (on different masters) get a chance.
                                $timePassed = microtime( true ) - $lastCheckTime;
@@ -221,12 +223,12 @@ class JobRunner implements LoggerAwareInterface {
                                                        'timeout' => self::MAX_ALLOWED_LAG
                                                ] );
                                        } catch ( DBReplicationWaitError $e ) {
-                                               $response['reached'] = 'slave-lag-limit';
+                                               $response['reached'] = 'replica-lag-limit';
                                                break;
                                        }
                                        $lastCheckTime = microtime( true );
                                }
-                               // Don't let any queue slaves/backups fall behind
+                               // Don't let any queue replica DBs/backups fall behind
                                if ( $jobsPopped > 0 && ( $jobsPopped % 100 ) == 0 ) {
                                        $group->waitForBackups();
                                }
@@ -252,27 +254,28 @@ class JobRunner implements LoggerAwareInterface {
 
        /**
         * @param Job $job
-        * @param BufferingStatsdDataFactory $stats
+        * @param LBFactory $lbFactory
+        * @param StatsdDataFactory $stats
         * @param float $popTime
         * @return array Map of status/error/timeMs
         */
-       private function executeJob( Job $job, $stats, $popTime ) {
+       private function executeJob( Job $job, LBFactory $lbFactory, $stats, $popTime ) {
                $jType = $job->getType();
                $msg = $job->toString() . " STARTING";
                $this->logger->debug( $msg );
                $this->debugCallback( $msg );
-               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
 
                // Run the job...
                $rssStart = $this->getMaxRssKb();
                $jobStartTime = microtime( true );
                try {
+                       $fnameTrxOwner = get_class( $job ) . '::run'; // give run() outer scope
+                       $lbFactory->beginMasterChanges( $fnameTrxOwner );
                        $status = $job->run();
                        $error = $job->getLastError();
-                       $this->commitMasterChanges( $job );
-
+                       $this->commitMasterChanges( $lbFactory, $job, $fnameTrxOwner );
+                       // Run any deferred update tasks; doUpdates() manages transactions itself
                        DeferredUpdates::doUpdates();
-                       $this->commitMasterChanges( $job );
                } catch ( Exception $e ) {
                        MWExceptionHandler::rollbackMasterChangesAndLog( $e );
                        $status = false;
@@ -288,10 +291,10 @@ class JobRunner implements LoggerAwareInterface {
 
                // Commit all outstanding connections that are in a transaction
                // to get a fresh repeatable read snapshot on every connection.
-               // Note that jobs are still responsible for handling slave lag.
-               $lbFactory->commitAll( __METHOD__ );
+               // Note that jobs are still responsible for handling replica DB lag.
+               $lbFactory->flushReplicaSnapshots( __METHOD__ );
                // Clear out title cache data from prior snapshots
-               LinkCache::singleton()->clear();
+               MediaWikiServices::getInstance()->getLinkCache()->clear();
                $timeMs = intval( ( microtime( true ) - $jobStartTime ) * 1000 );
                $rssEnd = $this->getMaxRssKb();
 
@@ -490,16 +493,18 @@ class JobRunner implements LoggerAwareInterface {
        /**
         * Issue a commit on all masters who are currently in a transaction and have
         * made changes to the database. It also supports sometimes waiting for the
-        * local wiki's slaves to catch up. See the documentation for
+        * local wiki's replica DBs to catch up. See the documentation for
         * $wgJobSerialCommitThreshold for more.
         *
+        * @param LBFactory $lbFactory
         * @param Job $job
+        * @param string $fnameTrxOwner
         * @throws DBError
         */
-       private function commitMasterChanges( Job $job ) {
+       private function commitMasterChanges( LBFactory $lbFactory, Job $job, $fnameTrxOwner ) {
                global $wgJobSerialCommitThreshold;
 
-               $lb = wfGetLB( wfWikiID() );
+               $lb = $lbFactory->getMainLB( wfWikiID() );
                if ( $wgJobSerialCommitThreshold !== false && $lb->getServerCount() > 1 ) {
                        // Generally, there is one master connection to the local DB
                        $dbwSerial = $lb->getAnyOpenConnection( $lb->getWriterIndex() );
@@ -513,12 +518,12 @@ class JobRunner implements LoggerAwareInterface {
                                $dbwSerial = false;
                        }
                } else {
-                       // There are no slaves or writes are all to foreign DB (we don't handle that)
+                       // There are no replica DBs or writes are all to foreign DB (we don't handle that)
                        $dbwSerial = false;
                }
 
                if ( !$dbwSerial ) {
-                       wfGetLBFactory()->commitMasterChanges( __METHOD__ );
+                       $lbFactory->commitMasterChanges( $fnameTrxOwner );
                        return;
                }
 
@@ -532,16 +537,18 @@ class JobRunner implements LoggerAwareInterface {
                        // This will trigger a rollback in the main loop
                        throw new DBError( $dbwSerial, "Timed out waiting on commit queue." );
                }
-               // Wait for the slave DBs to catch up
+               $unlocker = new ScopedCallback( function () use ( $dbwSerial ) {
+                       $dbwSerial->unlock( 'jobrunner-serial-commit', __METHOD__ );
+               } );
+
+               // Wait for the replica DBs to catch up
                $pos = $lb->getMasterPos();
                if ( $pos ) {
                        $lb->waitForAll( $pos );
                }
 
                // Actually commit the DB master changes
-               wfGetLBFactory()->commitMasterChanges( __METHOD__ );
-
-               // Release the lock
-               $dbwSerial->unlock( 'jobrunner-serial-commit', __METHOD__ );
+               $lbFactory->commitMasterChanges( $fnameTrxOwner );
+               ScopedCallback::consume( $unlocker );
        }
 }
index b561021..1828dd7 100644 (file)
@@ -19,6 +19,7 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Job to add recent change entries mentioning category membership changes
@@ -48,7 +49,8 @@ class CategoryMembershipChangeJob extends Job {
                        return false; // deleted?
                }
 
-               $dbw = wfGetDB( DB_MASTER );
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+               $dbw = $lb->getConnection( DB_MASTER );
                // Use a named lock so that jobs for this page see each others' changes
                $lockKey = "CategoryMembershipUpdates:{$page->getId()}";
                $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 10 );
@@ -57,14 +59,14 @@ class CategoryMembershipChangeJob extends Job {
                        return false;
                }
 
-               $dbr = wfGetDB( DB_SLAVE, [ 'recentchanges' ] );
-               // Wait till the slave is caught up so that jobs for this page see each others' changes
-               if ( !wfGetLB()->safeWaitForMasterPos( $dbr ) ) {
-                       $this->setLastError( "Timed out while waiting for slave to catch up" );
+               $dbr = wfGetDB( DB_REPLICA, [ 'recentchanges' ] );
+               // Wait till the replica DB is caught up so that jobs for this page see each others' changes
+               if ( !$lb->safeWaitForMasterPos( $dbr ) ) {
+                       $this->setLastError( "Timed out while waiting for replica DB to catch up" );
                        return false;
                }
                // Clear any stale REPEATABLE-READ snapshot
-               wfGetLBFactory()->commitAll( __METHOD__ );
+               $dbr->flushSnapshot( __METHOD__ );
 
                $cutoffUnix = wfTimestamp( TS_UNIX, $this->params['revTimestamp'] );
                // Using ENQUEUE_FUDGE_SEC handles jobs inserted out of revision order due to the delay
@@ -118,19 +120,23 @@ class CategoryMembershipChangeJob extends Job {
                );
 
                // Apply all category updates in revision timestamp order
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                foreach ( $res as $row ) {
-                       $this->notifyUpdatesForRevision( $page, Revision::newFromRow( $row ) );
+                       $this->notifyUpdatesForRevision( $lbFactory, $page, Revision::newFromRow( $row ) );
                }
 
                return true;
        }
 
        /**
+        * @param LBFactory $lbFactory
         * @param WikiPage $page
         * @param Revision $newRev
         * @throws MWException
         */
-       protected function notifyUpdatesForRevision( WikiPage $page, Revision $newRev ) {
+       protected function notifyUpdatesForRevision(
+               LBFactory $lbFactory, WikiPage $page, Revision $newRev
+       ) {
                $config = RequestContext::getMain()->getConfig();
                $title = $page->getTitle();
 
@@ -156,9 +162,7 @@ class CategoryMembershipChangeJob extends Job {
                        return; // nothing to do
                }
 
-               $dbw = wfGetDB( DB_MASTER );
-               $factory = wfGetLBFactory();
-               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
+               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
 
                $catMembChange = new CategoryMembershipChange( $title, $newRev );
                $catMembChange->checkTemplateLinks();
@@ -170,7 +174,7 @@ class CategoryMembershipChangeJob extends Job {
                        $categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
                        $catMembChange->triggerCategoryAddedNotification( $categoryTitle );
                        if ( $insertCount++ && ( $insertCount % $batchSize ) == 0 ) {
-                               $factory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
                        }
                }
 
@@ -178,7 +182,7 @@ class CategoryMembershipChangeJob extends Job {
                        $categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
                        $catMembChange->triggerCategoryRemovedNotification( $categoryTitle );
                        if ( $insertCount++ && ( $insertCount++ % $batchSize ) == 0 ) {
-                               $factory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
                        }
                }
        }
index 8d565bd..5c0f89f 100644 (file)
@@ -59,7 +59,7 @@ class DeleteLinksJob extends Job {
 
                $update = new LinksDeletionUpdate( $page, $pageId, $timestamp );
                $update->setTransactionTicket( $factory->getEmptyTransactionTicket( __METHOD__ ) );
-               DataUpdate::runUpdates( [ $update ] );
+               $update->doUpdate();
 
                return true;
        }
index 26d3c5c..80826fe 100644 (file)
@@ -31,7 +31,7 @@
  * @code
  * $ php maintenance/eval.php
  * > $queue = JobQueueGroup::singleton();
- * > $job = new NullJob( Title::newMainPage(), array( 'lives' => 10 ) );
+ * > $job = new NullJob( Title::newMainPage(), [ 'lives' => 10 ] );
  * > $queue->push( $job );
  * @endcode
  * You can then confirm the job has been enqueued by using the showJobs.php
index 2fd3899..809fb63 100644 (file)
@@ -93,7 +93,7 @@ class RecentChangesUpdateJob extends Job {
                        );
                        if ( $rcIds ) {
                                $dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
-                               // There might be more, so try waiting for slaves
+                               // There might be more, so try waiting for replica DBs
                                try {
                                        $factory->commitAndWaitForReplication(
                                                __METHOD__, $ticket, [ 'timeout' => 3 ]
index 9cdb161..a337da4 100644 (file)
@@ -40,7 +40,7 @@ class RefreshLinksJob extends Job {
        const PARSE_THRESHOLD_SEC = 1.0;
        /** @var integer Lag safety margin when comparing root job times to last-refresh times */
        const CLOCK_FUDGE = 10;
-       /** @var integer How many seconds to wait for slaves to catch up */
+       /** @var integer How many seconds to wait for replica DBs to catch up */
        const LAG_WAIT_TIMEOUT = 15;
 
        function __construct( Title $title, array $params ) {
@@ -83,7 +83,7 @@ class RefreshLinksJob extends Job {
 
                // Job to update all (or a range of) backlink pages for a page
                if ( !empty( $this->params['recursive'] ) ) {
-                       // When the base job branches, wait for the slaves to catch up to the master.
+                       // When the base job branches, wait for the replica DBs to catch up to the master.
                        // From then on, we know that any template changes at the time the base job was
                        // enqueued will be reflected in backlink page parses when the leaf jobs run.
                        if ( !isset( $params['range'] ) ) {
@@ -182,7 +182,7 @@ class RefreshLinksJob extends Job {
 
                        $skewedTimestamp = $this->params['rootJobTimestamp'];
                        if ( $opportunistic ) {
-                               // Neither clock skew nor DB snapshot/slave lag matter much for such
+                               // Neither clock skew nor DB snapshot/replica DB lag matter much for such
                                // updates; focus on reusing the (often recently updated) cache
                        } else {
                                // For transclusion updates, the template changes must be reflected
@@ -263,7 +263,9 @@ class RefreshLinksJob extends Job {
                        }
                }
 
-               DataUpdate::runUpdates( $updates );
+               foreach ( $updates as $update ) {
+                       $update->doUpdate();
+               }
 
                InfoAction::invalidateCache( $title );
 
index 2370ed3..90c9a75 100644 (file)
@@ -84,13 +84,15 @@ class MapCacheLRU {
         * If the item is already set, it will be pushed to the top of the cache.
         *
         * @param string $key
-        * @return mixed
+        * @return mixed Returns null if the key was not found
         */
        public function get( $key ) {
                if ( !array_key_exists( $key, $this->cache ) ) {
                        return null;
                }
+
                $this->ping( $key );
+
                return $this->cache[$key];
        }
 
@@ -102,6 +104,28 @@ class MapCacheLRU {
                return array_keys( $this->cache );
        }
 
+       /**
+        * Get an item with the given key, producing and setting it if not found.
+        *
+        * If the callback returns false, then nothing is stored.
+        *
+        * @since 1.28
+        * @param string $key
+        * @param callable $callback Callback that will produce the value
+        * @return mixed The cached value if found or the result of $callback otherwise
+        */
+       public function getWithSetCallback( $key, callable $callback ) {
+               $value = $this->get( $key );
+               if ( $value === null ) {
+                       $value = call_user_func( $callback );
+                       if ( $value !== false ) {
+                               $this->set( $key, $value );
+                       }
+               }
+
+               return $value;
+       }
+
        /**
         * Clear one or several cache entries, or all cache entries
         *
index 2e780c9..dd1976c 100644 (file)
@@ -30,6 +30,19 @@ use Liuggio\StatsdClient\Entity\StatsdDataInterface;
  * @since 1.26
  */
 class SamplingStatsdClient extends StatsdClient {
+       protected $samplingRates = [];
+
+       /**
+        * Sampling rates as an associative array of patterns and rates.
+        * Patterns are Unix shell patterns (e.g. 'MediaWiki.api.*').
+        * Rates are sampling probabilities (e.g. 0.1 means 1 in 10 events are sampled).
+        * @param array $samplingRates
+        * @since 1.28
+        */
+       public function setSamplingRates( array $samplingRates ) {
+               $this->samplingRates = $samplingRates;
+       }
+
        /**
         * Sets sampling rate for all items in $data.
         * The sample rate specified in a StatsdData entity overrides the sample rate specified here.
@@ -37,11 +50,18 @@ class SamplingStatsdClient extends StatsdClient {
         * {@inheritDoc}
         */
        public function appendSampleRate( $data, $sampleRate = 1 ) {
-               if ( $sampleRate < 1 ) {
-                       array_walk( $data, function( $item ) use ( $sampleRate ) {
+               $samplingRates = $this->samplingRates;
+               if ( !$samplingRates && $sampleRate !== 1 ) {
+                       $samplingRates = [ '*' => $sampleRate ];
+               }
+               if ( $samplingRates ) {
+                       array_walk( $data, function( $item ) use ( $samplingRates ) {
                                /** @var $item StatsdData */
-                               if ( $item->getSampleRate() === 1 ) {
-                                       $item->setSampleRate( $sampleRate );
+                               foreach ( $samplingRates as $pattern => $rate ) {
+                                       if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) {
+                                               $item->setSampleRate( $item->getSampleRate() * $rate );
+                                               break;
+                                       }
                                }
                        } );
                }
@@ -74,9 +94,7 @@ class SamplingStatsdClient extends StatsdClient {
                }
 
                // add sampling
-               if ( $sampleRate < 1 ) {
-                       $data = $this->appendSampleRate( $data, $sampleRate );
-               }
+               $data = $this->appendSampleRate( $data, $sampleRate );
                $data = $this->sampleData( $data );
 
                $data = array_map( 'strval', $data );
index 3cc6236..3339eb3 100644 (file)
@@ -37,8 +37,9 @@ class WaitConditionLoop {
 
        const CONDITION_REACHED = 1;
        const CONDITION_CONTINUE = 0; // evaluates as falsey
-       const CONDITION_TIMED_OUT = -1;
-       const CONDITION_ABORTED = -2;
+       const CONDITION_FAILED = -1;
+       const CONDITION_TIMED_OUT = -2;
+       const CONDITION_ABORTED = -3;
 
        /**
         * @param callable $condition Callback that returns a WaitConditionLoop::CONDITION_ constant
@@ -53,11 +54,14 @@ class WaitConditionLoop {
 
        /**
         * Invoke the loop and continue until either:
-        *   - a) The condition callback does not return either CONDITION_CONTINUE or true
+        *   - a) The condition callback returns neither CONDITION_CONTINUE nor false
         *   - b) The timeout is reached
         * This a condition callback can return true (stop) or false (continue) for convenience.
         * In such cases, the halting result of "true" will be converted to CONDITION_REACHED.
         *
+        * If $timeout is 0, then only the condition callback will be called (no busy callbacks),
+        * and this will immediately return CONDITION_FAILED if the condition was not met.
+        *
         * Exceptions in callbacks will be caught and the callback will be swapped with
         * one that simply rethrows that exception back to the caller when invoked.
         *
@@ -77,12 +81,19 @@ class WaitConditionLoop {
                        $checkResult = call_user_func( $this->condition );
                        $cpu = $this->getCpuTime() - $cpuStart;
                        $real = $this->getWallTime() - $realStart;
-                       // Exit if the condition is reached
-                       if ( (int)$checkResult !== self::CONDITION_CONTINUE ) {
-                               $finalResult = is_int( $checkResult ) ? $checkResult : self::CONDITION_REACHED;
+                       // Exit if the condition is reached, and error occurs, or this is non-blocking
+                       if ( $this->timeout <= 0 ) {
+                               $finalResult = $checkResult ? self::CONDITION_REACHED : self::CONDITION_FAILED;
+                               break;
+                       } elseif ( (int)$checkResult !== self::CONDITION_CONTINUE ) {
+                               if ( is_int( $checkResult ) ) {
+                                       $finalResult = $checkResult;
+                               } else {
+                                       $finalResult = self::CONDITION_REACHED;
+                               }
                                break;
                        } elseif ( $lastCheck ) {
-                               break; // timeout
+                               break; // timeout reached
                        }
                        // Detect if condition callback seems to block or if justs burns CPU
                        $conditionUsesInterrupts = ( $real > 0.100 && $cpu <= $real * .03 );
index a679be8..cd79b67 100644 (file)
@@ -410,35 +410,21 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
                }
 
                $expiry = min( $expiry ?: INF, self::TTL_DAY );
-
-               $this->clearLastError();
-               $timestamp = microtime( true ); // starting UNIX timestamp
-               if ( $this->add( "{$key}:lock", 1, $expiry ) ) {
-                       $locked = true;
-               } elseif ( $this->getLastError() || $timeout <= 0 ) {
-                       $locked = false; // network partition or non-blocking
-               } else {
-                       // Estimate the RTT (us); use 1ms minimum for sanity
-                       $uRTT = max( 1e3, ceil( 1e6 * ( microtime( true ) - $timestamp ) ) );
-                       $sleep = 2 * $uRTT; // rough time to do get()+set()
-
-                       $attempts = 0; // failed attempts
-                       do {
-                               if ( ++$attempts >= 3 && $sleep <= 5e5 ) {
-                                       // Exponentially back off after failed attempts to avoid network spam.
-                                       // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts.
-                                       $sleep *= 2;
-                               }
-                               usleep( $sleep ); // back off
+               $loop = new WaitConditionLoop(
+                       function () use ( $key, $timeout, $expiry ) {
                                $this->clearLastError();
-                               $locked = $this->add( "{$key}:lock", 1, $expiry );
-                               if ( $this->getLastError() ) {
-                                       $locked = false; // network partition
-                                       break;
+                               if ( $this->add( "{$key}:lock", 1, $expiry ) ) {
+                                       return true; // locked!
+                               } elseif ( $this->getLastError() ) {
+                                       return WaitConditionLoop::CONDITION_ABORTED; // network partition?
                                }
-                       } while ( !$locked && ( microtime( true ) - $timestamp ) < $timeout );
-               }
 
+                               return WaitConditionLoop::CONDITION_CONTINUE;
+                       },
+                       $timeout
+               );
+
+               $locked = ( $loop->invoke() === $loop::CONDITION_REACHED );
                if ( $locked ) {
                        $this->locks[$key] = [ 'class' => $rclass, 'depth' => 1 ];
                }
index e70a51f..74bf4b5 100644 (file)
@@ -42,6 +42,8 @@ class CachedBagOStuff extends HashBagOStuff {
         * @param array $params Parameters for HashBagOStuff
         */
        function __construct( BagOStuff $backend, $params = [] ) {
+               unset( $params['reportDupes'] ); // useless here
+
                parent::__construct( $params );
 
                $this->backend = $backend;
index f2ba9de..3bc7ae2 100644 (file)
@@ -22,7 +22,7 @@
 
 /**
  * A cache class that directs writes to one set of servers and reads to
- * another. This assumes that the servers used for reads are setup to slave
+ * another. This assumes that the servers used for reads are setup to replica DB
  * those that writes go to. This can easily be used with redis for example.
  *
  * In the WAN scenario (e.g. multi-datacenter case), this is useful when
@@ -42,7 +42,7 @@ class ReplicatedBagOStuff extends BagOStuff {
         *   - writeFactory : ObjectFactory::getObjectFromSpec array yeilding BagOStuff.
         *                    This object will be used for writes (e.g. the master DB).
         *   - readFactory  : ObjectFactory::getObjectFromSpec array yeilding BagOStuff.
-        *                    This object will be used for reads (e.g. a slave DB).
+        *                    This object will be used for reads (e.g. a replica DB).
         *
         * @param array $params
         * @throws InvalidArgumentException
@@ -59,12 +59,13 @@ class ReplicatedBagOStuff extends BagOStuff {
                                __METHOD__ . ': the "readFactory" parameter is required' );
                }
 
+               $opts = [ 'reportDupes' => false ]; // redundant
                $this->writeStore = ( $params['writeFactory'] instanceof BagOStuff )
                        ? $params['writeFactory']
-                       : ObjectFactory::getObjectFromSpec( $params['writeFactory'] );
+                       : ObjectFactory::getObjectFromSpec( $opts + $params['writeFactory'] );
                $this->readStore = ( $params['readFactory'] instanceof BagOStuff )
                        ? $params['readFactory']
-                       : ObjectFactory::getObjectFromSpec( $params['readFactory'] );
+                       : ObjectFactory::getObjectFromSpec( $opts + $params['readFactory'] );
                $this->attrMap = $this->mergeFlagMaps( [ $this->readStore, $this->writeStore ] );
        }
 
index 0d7da91..5f6e324 100644 (file)
@@ -76,8 +76,8 @@ use Psr\Log\NullLogger;
 class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        /** @var BagOStuff The local datacenter cache */
        protected $cache;
-       /** @var HashBagOStuff Script instance PHP cache */
-       protected $procCache;
+       /** @var HashBagOStuff[] Map of group PHP instance caches */
+       protected $processCaches = [];
        /** @var string Purge channel name */
        protected $purgeChannel;
        /** @var EventRelayer Bus that handles purge broadcasts */
@@ -104,6 +104,15 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        /** Default time-since-expiry on a miss that makes a key "hot" */
        const LOCK_TSE = 1;
 
+       /** Never consider performing "popularity" refreshes until a key reaches this age */
+       const AGE_NEW = 60;
+       /** The time length of the "popularity" refresh window for hot keys */
+       const HOT_TTR = 900;
+       /** Hits/second for a refresh to be expected within the "popularity" window */
+       const HIT_RATE_HIGH = 1;
+       /** Seconds to ramp up to the "popularity" refresh chance after a key is no longer new */
+       const RAMPUP_TTL = 30;
+
        /** Idiom for getWithSetCallback() callbacks to avoid calling set() */
        const TTL_UNCACHEABLE = -1;
        /** Idiom for getWithSetCallback() callbacks to 'lockTSE' logic */
@@ -112,6 +121,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        const TTL_LAGGED = 30;
        /** Idiom for delete() for "no hold-off" */
        const HOLDOFF_NONE = 0;
+       /** Idiom for getWithSetCallback() for "no minimum required as-of timestamp" */
+       const MIN_TIMESTAMP_NONE = 0.0;
 
        /** Tiny negative float to use when CTL comes up >= 0 due to clock skew */
        const TINY_NEGATIVE = -0.000001;
@@ -145,7 +156,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        const VFLD_DATA = 'WOC:d'; // key to the value of versioned data
        const VFLD_VERSION = 'WOC:v'; // key to the version of the value present
 
-       const MAX_PC_KEYS = 1000; // max keys to keep in process cache
+       const PC_PRIMARY = 'primary:1000'; // process cache name and max key count
 
        const DEFAULT_PURGE_CHANNEL = 'wancache-purge';
 
@@ -158,7 +169,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         */
        public function __construct( array $params ) {
                $this->cache = $params['cache'];
-               $this->procCache = new HashBagOStuff( [ 'maxKeys' => self::MAX_PC_KEYS ] );
                $this->purgeChannel = isset( $params['channels']['purge'] )
                        ? $params['channels']['purge']
                        : self::DEFAULT_PURGE_CHANNEL;
@@ -364,7 +374,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *
         * Example usage:
         * @code
-        *     $dbr = wfGetDB( DB_SLAVE );
+        *     $dbr = wfGetDB( DB_REPLICA );
         *     $setOpts = Database::getCacheSetOptions( $dbr );
         *     // Fetch the row from the DB
         *     $row = $dbr->selectRow( ... );
@@ -377,22 +387,30 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @param integer $ttl Seconds to live. Special values are:
         *   - WANObjectCache::TTL_INDEFINITE: Cache forever
         * @param array $opts Options map:
-        *   - lag     : Seconds of slave lag. Typically, this is either the slave lag
-        *               before the data was read or, if applicable, the slave lag before
-        *               the snapshot-isolated transaction the data was read from started.
-        *               Default: 0 seconds
-        *   - since   : UNIX timestamp of the data in $value. Typically, this is either
-        *               the current time the data was read or (if applicable) the time when
-        *               the snapshot-isolated transaction the data was read from started.
-        *               Default: 0 seconds
+        *   - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag
+        *      before the data was read or, if applicable, the replica DB lag before
+        *      the snapshot-isolated transaction the data was read from started.
+        *      Use false to indicate that replication is not running.
+        *      Default: 0 seconds
+        *   - since : UNIX timestamp of the data in $value. Typically, this is either
+        *      the current time the data was read or (if applicable) the time when
+        *      the snapshot-isolated transaction the data was read from started.
+        *      Default: 0 seconds
         *   - pending : Whether this data is possibly from an uncommitted write transaction.
-        *               Generally, other threads should not see values from the future and
-        *               they certainly should not see ones that ended up getting rolled back.
-        *               Default: false
+        *      Generally, other threads should not see values from the future and
+        *      they certainly should not see ones that ended up getting rolled back.
+        *      Default: false
         *   - lockTSE : if excessive replication/snapshot lag is detected, then store the value
-        *               with this TTL and flag it as stale. This is only useful if the reads for
-        *               this key use getWithSetCallback() with "lockTSE" set.
-        *               Default: WANObjectCache::TSE_NONE
+        *      with this TTL and flag it as stale. This is only useful if the reads for
+        *      this key use getWithSetCallback() with "lockTSE" set.
+        *      Default: WANObjectCache::TSE_NONE
+        *   - staleTTL : Seconds to keep the key around if it is stale. The get()/getMulti()
+        *      methods return such stale values with a $curTTL of 0, and getWithSetCallback()
+        *      will call the regeneration callback in such cases, passing in the old value
+        *      and its as-of time to the callback. This is useful if adaptiveTTL() is used
+        *      on the old value's as-of time when it is verified as still being correct.
+        *      Default: 0.
+        * @note Options added in 1.28: staleTTL
         * @return bool Success
         */
        final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
@@ -400,6 +418,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
                $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
                $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
+               $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : 0;
 
                // Do not cache potentially uncommitted data as it might get rolled back
                if ( !empty( $opts['pending'] ) ) {
@@ -441,7 +460,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                                : $wrapped;
                };
 
-               return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl, 1 );
+               return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
        }
 
        /**
@@ -566,7 +585,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * Keys using it via get(), getMulti(), or getWithSetCallback() will
         * be invalidated. It is treated as being HOLDOFF_TTL seconds in the future
         * by those methods to avoid race conditions where dependent keys get updated
-        * with stale values (e.g. from a DB slave).
+        * with stale values (e.g. from a DB replica DB).
         *
         * This is typically useful for keys with hardcoded names or in some cases
         * dynamically generated names where a low number of combinations exist.
@@ -635,6 +654,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *   - $oldValue : current cache value or false if not present
         *   - &$ttl : a reference to the TTL which can be altered
         *   - &$setOpts : a reference to options for set() which can be altered
+        *   - $oldAsOf : generation UNIX timestamp of $oldValue or null if not present (since 1.28)
         *
         * It is strongly recommended to set the 'lag' and 'since' fields to avoid race conditions
         * that can cause stale values to get stuck at keys. Usually, callbacks ignore the current
@@ -660,8 +680,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *         $cache::TTL_MINUTE,
         *         // Function that derives the new key value
         *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_SLAVE );
-        *             // Account for any snapshot/slave lag
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
         *             $setOpts += Database::getCacheSetOptions( $dbr );
         *
         *             return $dbr->selectRow( ... );
@@ -678,8 +698,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *         $cache::TTL_DAY,
         *         // Function that derives the new key value
         *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_SLAVE );
-        *             // Account for any snapshot/slave lag
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
         *             $setOpts += Database::getCacheSetOptions( $dbr );
         *
         *             return CatConfig::newFromRow( $dbr->selectRow( ... ) );
@@ -705,8 +725,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *         // Function that derives the new key value
         *         function ( $oldValue, &$ttl, array &$setOpts ) {
         *             // Determine new value from the DB
-        *             $dbr = wfGetDB( DB_SLAVE );
-        *             // Account for any snapshot/slave lag
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
         *             $setOpts += Database::getCacheSetOptions( $dbr );
         *
         *             return CatState::newFromResults( $dbr->select( ... ) );
@@ -732,8 +752,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *         10,
         *         // Function that derives the new key value
         *         function ( $oldValue, &$ttl, array &$setOpts ) {
-        *             $dbr = wfGetDB( DB_SLAVE );
-        *             // Account for any snapshot/slave lag
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
         *             $setOpts += Database::getCacheSetOptions( $dbr );
         *
         *             // Start off with the last cached list
@@ -765,9 +785,6 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *   - checkKeys: List of "check" keys. The key at $key will be seen as invalid when either
         *      touchCheckKey() or resetCheckKey() is called on any of these keys.
         *      Default: [].
-        *   - lowTTL: Consider pre-emptive updates when the current TTL (seconds) of the key is less
-        *      than this. It becomes more likely over time, becoming certain once the key is expired.
-        *      Default: WANObjectCache::LOW_TTL.
         *   - lockTSE: If the key is tombstoned or expired (by checkKeys) less than this many seconds
         *      ago, then try to have a single thread handle cache regeneration at any given time.
         *      Other threads will try to use stale values if possible. If, on miss, the time since
@@ -784,26 +801,53 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *   - pcTTL: Process cache the value in this PHP instance for this many seconds. This avoids
         *      network I/O when a key is read several times. This will not cache when the callback
         *      returns false, however. Note that any purges will not be seen while process cached;
-        *      since the callback should use slave DBs and they may be lagged or have snapshot
+        *      since the callback should use replica DBs and they may be lagged or have snapshot
         *      isolation anyway, this should not typically matter.
         *      Default: WANObjectCache::TTL_UNCACHEABLE.
+        *   - pcGroup: Process cache group to use instead of the primary one. If set, this must be
+        *      of the format ALPHANUMERIC_NAME:MAX_KEY_SIZE, e.g. "mydata:10". Use this for storing
+        *      large values, small yet numerous values, or some values with a high cost of eviction.
+        *      It is generally preferable to use a class constant when setting this value.
+        *      This has no effect unless pcTTL is used.
+        *      Default: WANObjectCache::PC_PRIMARY.
         *   - version: Integer version number. This allows for callers to make breaking changes to
         *      how values are stored while maintaining compatability and correct cache purges. New
         *      versions are stored alongside older versions concurrently. Avoid storing class objects
         *      however, as this reduces compatibility (due to serialization).
         *      Default: null.
+        *   - minAsOf: Reject values if they were generated before this UNIX timestamp.
+        *      This is useful if the source of a key is suspected of having possibly changed
+        *      recently, and the caller wants any such changes to be reflected.
+        *      Default: WANObjectCache::MIN_TIMESTAMP_NONE.
+        *   - hotTTR: Expected time-till-refresh for keys that average ~1 hit/second.
+        *      This should be greater than "ageNew". Keys with higher hit rates will regenerate
+        *      more often. This is useful when a popular key is changed but the cache purge was
+        *      delayed or lost. Seldom used keys are rarely affected by this setting, unless an
+        *      extremely low "hotTTR" value is passed in.
+        *      Default: WANObjectCache::HOT_TTR.
+        *   - lowTTL: Consider pre-emptive updates when the current TTL (seconds) of the key is less
+        *      than this. It becomes more likely over time, becoming certain once the key is expired.
+        *      Default: WANObjectCache::LOW_TTL.
+        *   - ageNew: Consider popularity refreshes only once a key reaches this age in seconds.
+        *      Default: WANObjectCache::AGE_NEW.
         * @return mixed Value found or written to the key
+        * @note Options added in 1.28: version, busyValue, hotTTR, ageNew, pcGroup, minAsOf
         * @note Callable type hints are not used to avoid class-autoloading
         */
        final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
                $pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] : self::TTL_UNCACHEABLE;
 
                // Try the process cache if enabled
-               $value = ( $pcTTL >= 0 ) ? $this->procCache->get( $key ) : false;
+               if ( $pcTTL >= 0 ) {
+                       $group = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
+                       $procCache = $this->getProcessCache( $group );
+                       $value = $procCache->get( $key );
+               } else {
+                       $procCache = false;
+                       $value = false;
+               }
 
                if ( $value === false ) {
-                       unset( $opts['minTime'] ); // not a public feature
-
                        // Fetch the value over the network
                        if ( isset( $opts['version'] ) ) {
                                $version = $opts['version'];
@@ -811,7 +855,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                                $cur = $this->doGetWithSetCallback(
                                        $key,
                                        $ttl,
-                                       function ( $oldValue, &$ttl, &$setOpts ) use ( $callback, $version ) {
+                                       function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
+                                       use ( $callback, $version ) {
                                                if ( is_array( $oldValue )
                                                        && array_key_exists( self::VFLD_DATA, $oldValue )
                                                ) {
@@ -822,7 +867,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                                                }
 
                                                return [
-                                                       self::VFLD_DATA => $callback( $oldData, $ttl, $setOpts ),
+                                                       self::VFLD_DATA => $callback( $oldData, $ttl, $setOpts, $oldAsOf ),
                                                        self::VFLD_VERSION => $version
                                                ];
                                        },
@@ -840,7 +885,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                                                $ttl,
                                                $callback,
                                                // Regenerate value if not newer than $key
-                                               [ 'version' => null, 'minTime' => $asOf ] + $opts
+                                               [ 'version' => null, 'minAsOf' => $asOf ] + $opts
                                        );
                                }
                        } else {
@@ -848,8 +893,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        }
 
                        // Update the process cache if enabled
-                       if ( $pcTTL >= 0 && $value !== false ) {
-                               $this->procCache->set( $key, $value, $pcTTL );
+                       if ( $procCache && $value !== false ) {
+                               $procCache->set( $key, $value, $pcTTL );
                        }
                }
 
@@ -864,8 +909,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @param string $key
         * @param integer $ttl
         * @param callback $callback
-        * @param array $opts Options map for getWithSetCallback() which also includes:
-        *   - minTime: Treat values older than this UNIX timestamp as not existing. Default: null.
+        * @param array $opts Options map for getWithSetCallback()
         * @param float &$asOf Cache generation timestamp of returned value [returned]
         * @return mixed
         * @note Callable type hints are not used to avoid class-autoloading
@@ -875,7 +919,9 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
                $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
                $busyValue = isset( $opts['busyValue'] ) ? $opts['busyValue'] : null;
-               $minTime = isset( $opts['minTime'] ) ? $opts['minTime'] : 0.0;
+               $popWindow = isset( $opts['hotTTR'] ) ? $opts['hotTTR'] : self::HOT_TTR;
+               $ageNew = isset( $opts['ageNew'] ) ? $opts['ageNew'] : self::AGE_NEW;
+               $minTime = isset( $opts['minAsOf'] ) ? $opts['minAsOf'] : self::MIN_TIMESTAMP_NONE;
                $versioned = isset( $opts['version'] );
 
                // Get the current key value
@@ -887,7 +933,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                if ( $value !== false
                        && $curTTL > 0
                        && $this->isValid( $value, $versioned, $asOf, $minTime )
-                       && !$this->worthRefresh( $curTTL, $lowTTL )
+                       && !$this->worthRefreshExpiring( $curTTL, $lowTTL )
+                       && !$this->worthRefreshPopular( $asOf, $ageNew, $popWindow )
                ) {
                        return $value;
                }
@@ -937,13 +984,13 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
 
                // Generate the new value from the callback...
                $setOpts = [];
-               $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts ] );
-               $asOf = microtime( true );
+               $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
                // When delete() is called, writes are write-holed by the tombstone,
                // so use a special INTERIM key to pass the new value around threads.
                if ( ( $isTombstone && $lockTSE > 0 ) && $value !== false && $ttl >= 0 ) {
                        $tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
-                       $wrapped = $this->wrap( $value, $tempTTL, $asOf );
+                       $newAsOf = microtime( true );
+                       $wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
                        // Avoid using set() to avoid pointless mcrouter broadcasting
                        $this->cache->merge(
                                self::INTERIM_KEY_PREFIX . $key,
@@ -995,7 +1042,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         */
        final public function getLastError() {
                if ( $this->lastRelayError ) {
-                       // If the cache and the relayer failed, focus on the later.
+                       // If the cache and the relayer failed, focus on the latter.
                        // An update not making it to the relayer means it won't show up
                        // in other DCs (nor will consistent re-hashing see up-to-date values).
                        // On the other hand, if just the cache update failed, then it should
@@ -1030,7 +1077,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @since 1.27
         */
        public function clearProcessCache() {
-               $this->procCache->clear();
+               $this->processCaches = [];
        }
 
        /**
@@ -1066,8 +1113,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @since 1.28
         */
        public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = .2 ) {
-               if ( is_float( $mtime ) ) {
-                       $mtime = (int)$mtime; // ignore fractional seconds
+               if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
+                       $mtime = (int)$mtime; // handle fractional seconds and string integers
                }
 
                if ( !is_int( $mtime ) || $mtime <= 0 ) {
@@ -1151,7 +1198,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @param float $lowTTL Consider a refresh when $curTTL is less than this
         * @return bool
         */
-       protected function worthRefresh( $curTTL, $lowTTL ) {
+       protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
                if ( $curTTL >= $lowTTL ) {
                        return false;
                } elseif ( $curTTL <= 0 ) {
@@ -1163,6 +1210,40 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
        }
 
+       /**
+        * Check if a key is due for randomized regeneration due to its popularity
+        *
+        * This is used so that popular keys can preemptively refresh themselves for higher
+        * consistency (especially in the case of purge loss/delay). Unpopular keys can remain
+        * in cache with their high nominal TTL. This means popular keys keep good consistency,
+        * whether the data changes frequently or not, and long-tail keys get to stay in cache
+        * and get hits too. Similar to worthRefreshExpiring(), randomization is used.
+        *
+        * @param float $asOf UNIX timestamp of the value
+        * @param integer $ageNew Age of key when this might recommend refreshing (seconds)
+        * @param integer $timeTillRefresh Age of key when it should be refreshed if popular (seconds)
+        * @return bool
+        */
+       protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh ) {
+               $age = microtime( true ) - $asOf;
+               $timeOld = $age - $ageNew;
+               if ( $timeOld <= 0 ) {
+                       return false;
+               }
+
+               // Lifecycle is: new, ramp-up refresh chance, full refresh chance
+               $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::RAMPUP_TTL / 2, 1 );
+               // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
+               // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1
+               // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
+               $chance = 1 / ( self::HIT_RATE_HIGH * $refreshWindowSec );
+
+               // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
+               $chance *= ( $timeOld <= self::RAMPUP_TTL ) ? $timeOld / self::RAMPUP_TTL : 1;
+
+               return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
+       }
+
        /**
         * Check whether $value is appropriately versioned and not older than $minTime (if set)
         *
@@ -1286,4 +1367,17 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        protected function makePurgeValue( $timestamp, $holdoff ) {
                return self::PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
        }
+
+       /**
+        * @param string $group
+        * @return HashBagOStuff
+        */
+       protected function getProcessCache( $group ) {
+               if ( !isset( $this->processCaches[$group] ) ) {
+                       list( , $n ) = explode( ':', $group );
+                       $this->processCaches[$group] = new HashBagOStuff( [ 'maxKeys' => (int)$n ] );
+               }
+
+               return $this->processCaches[$group];
+       }
 }
index 20d0217..7746d99 100644 (file)
@@ -705,39 +705,41 @@ class ManualLogEntry extends LogEntryBase {
         *
         * @param int $newId Id of the log entry.
         * @param string $to One of: rcandudp (default), rc, udp
-        * @return RecentChange|null
         */
        public function publish( $newId, $to = 'rcandudp' ) {
-               $log = new LogPage( $this->getType() );
-               if ( $log->isRestricted() ) {
-                       return null;
-               }
-
-               $rc = $this->getRecentChange( $newId );
-
-               if ( $to === 'rc' || $to === 'rcandudp' ) {
-                       $rc->save( 'pleasedontudp' );
-               }
-
-               if ( $to === 'udp' || $to === 'rcandudp' ) {
-                       $rc->notifyRCFeeds();
-               }
-
-               // Log the autopatrol if the log entry is patrollable
-               if ( $this->getIsPatrollable() &&
-                       $rc->getAttribute( 'rc_patrolled' ) === 1 ) {
-                       PatrolLog::record( $rc, true, $this->getPerformer() );
-               }
-
-               // Add change tags to the log entry and (if applicable) the associated revision
-               $tags = $this->getTags();
-               if ( !is_null( $tags ) ) {
-                       $rcId = $rc->getAttribute( 'rc_id' );
-                       $revId = $this->getAssociatedRevId(); // Use null if $revId is 0
-                       ChangeTags::addTags( $tags, $rcId, $revId > 0 ? $revId : null, $newId );
-               }
-
-               return $rc;
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( $newId, $to ) {
+                               $log = new LogPage( $this->getType() );
+                               if ( !$log->isRestricted() ) {
+                                       $rc = $this->getRecentChange( $newId );
+
+                                       if ( $to === 'rc' || $to === 'rcandudp' ) {
+                                               $rc->save( 'pleasedontudp' );
+                                       }
+
+                                       if ( $to === 'udp' || $to === 'rcandudp' ) {
+                                               $rc->notifyRCFeeds();
+                                       }
+
+                                       // Log the autopatrol if the log entry is patrollable
+                                       if ( $this->getIsPatrollable() &&
+                                               $rc->getAttribute( 'rc_patrolled' ) === 1
+                                       ) {
+                                               PatrolLog::record( $rc, true, $this->getPerformer() );
+                                       }
+
+                                       // Add change tags to the log entry and (if applicable) the associated revision
+                                       $tags = $this->getTags();
+                                       if ( !is_null( $tags ) ) {
+                                               $rcId = $rc->getAttribute( 'rc_id' );
+                                               $revId = $this->getAssociatedRevId(); // Use null if $revId is 0
+                                               ChangeTags::addTags( $tags, $rcId, $revId > 0 ? $revId : null, $newId );
+                                       }
+                               }
+                       },
+                       DeferredUpdates::POSTSEND,
+                       wfGetDB( DB_MASTER )
+               );
        }
 
        public function getType() {
index eeea789..68163c1 100644 (file)
@@ -78,7 +78,7 @@ class LogPager extends ReverseChronologicalPager {
                $this->getDateCond( $year, $month );
                $this->mTagFilter = $tagFilter;
 
-               $this->mDb = wfGetDB( DB_SLAVE, 'logpager' );
+               $this->mDb = wfGetDB( DB_REPLICA, 'logpager' );
        }
 
        public function getDefaultQuery() {
index bcdf62f..1e0013f 100644 (file)
@@ -39,7 +39,7 @@ use MediaWiki\Services\ServiceDisabledException;
  *        stored anywhere else (e.g. a "hoard" of objects).
  *
  * The former should always use strongly consistent stores, so callers don't
- * have to deal with stale reads. The later may be eventually consistent, but
+ * have to deal with stale reads. The latter may be eventually consistent, but
  * callers can use BagOStuff:READ_LATEST to see the latest available data.
  *
  * Primary entry points:
@@ -65,7 +65,7 @@ use MediaWiki\Services\ServiceDisabledException;
  *   Purpose: Ephemeral global storage.
  *   Stored centrally within the primary data-center.
  *   Changes are applied there first and replicated to other DCs (best-effort).
- *   To retrieve the latest value (e.g. not from a slave), use BagOStuff::READ_LATEST.
+ *   To retrieve the latest value (e.g. not from a replica DB), use BagOStuff::READ_LATEST.
  *   This store may be subject to LRU style evictions.
  *
  * - ObjectCache::getInstance( $cacheType )
index c3e0c96..f9d201f 100644 (file)
@@ -363,7 +363,7 @@ class RedisBagOStuff extends BagOStuff {
                                try {
                                        if ( $this->getMasterLinkStatus( $conn ) === 'down' ) {
                                                // If the master cannot be reached, fail-over to the next server.
-                                               // If masters are in data-center A, and slaves in data-center B,
+                                               // If masters are in data-center A, and replica DBs in data-center B,
                                                // this helps avoid the case were fail-over happens in A but not
                                                // to the corresponding server in B (e.g. read/write mismatch).
                                                continue;
@@ -384,10 +384,10 @@ class RedisBagOStuff extends BagOStuff {
        }
 
        /**
-        * Check the master link status of a Redis server that is configured as a slave.
+        * Check the master link status of a Redis server that is configured as a replica DB.
         * @param RedisConnRef $conn
         * @return string|null Master link status (either 'up' or 'down'), or null
-        *  if the server is not a slave.
+        *  if the server is not a replica DB.
         */
        protected function getMasterLinkStatus( RedisConnRef $conn ) {
                $info = $conn->info();
index 0abe64c..3baae50 100644 (file)
@@ -44,7 +44,7 @@ class SqlBagOStuff extends BagOStuff {
        /** @var string */
        protected $tableName = 'objectcache';
        /** @var bool */
-       protected $slaveOnly = false;
+       protected $replicaOnly = false;
        /** @var int */
        protected $syncTimeout = 3;
 
@@ -85,11 +85,11 @@ class SqlBagOStuff extends BagOStuff {
         *                  required to hold the largest shard index. Data will be
         *                  distributed across all tables by key hash. This is for
         *                  MySQL bugs 61735 and 61736.
-        *   - slaveOnly:   Whether to only use slave DBs and avoid triggering
+        *   - slaveOnly:   Whether to only use replica DBs and avoid triggering
         *                  garbage collection logic of expired items. This only
         *                  makes sense if the primary DB is used and only if get()
         *                  calls will be used. This is used by ReplicatedBagOStuff.
-        *   - syncTimeout: Max seconds to wait for slaves to catch up for WRITE_SYNC.
+        *   - syncTimeout: Max seconds to wait for replica DBs to catch up for WRITE_SYNC.
         *
         * @param array $params
         */
@@ -132,7 +132,7 @@ class SqlBagOStuff extends BagOStuff {
                if ( isset( $params['syncTimeout'] ) ) {
                        $this->syncTimeout = $params['syncTimeout'];
                }
-               $this->slaveOnly = !empty( $params['slaveOnly'] );
+               $this->replicaOnly = !empty( $params['slaveOnly'] );
        }
 
        protected function getSeparateMainLB() {
@@ -183,7 +183,7 @@ class SqlBagOStuff extends BagOStuff {
                                $db = DatabaseBase::factory( $type, $info );
                                $db->clearFlag( DBO_TRX );
                        } else {
-                               $index = $this->slaveOnly ? DB_SLAVE : DB_MASTER;
+                               $index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
                                if ( $this->getSeparateMainLB() ) {
                                        $db = $this->getSeparateMainLB()->getConnection( $index );
                                        $db->clearFlag( DBO_TRX ); // auto-commit mode
@@ -539,7 +539,7 @@ class SqlBagOStuff extends BagOStuff {
        }
 
        protected function garbageCollect() {
-               if ( !$this->purgePeriod || $this->slaveOnly ) {
+               if ( !$this->purgePeriod || $this->replicaOnly ) {
                        // Disabled
                        return;
                }
@@ -807,10 +807,10 @@ class SqlBagOStuff extends BagOStuff {
                        ?: MediaWikiServices::getInstance()->getDBLoadBalancer();
 
                if ( $lb->getServerCount() <= 1 ) {
-                       return true; // no slaves
+                       return true; // no replica DBs
                }
 
-               // Main LB is used; wait for any slaves to catch up
+               // Main LB is used; wait for any replica DBs to catch up
                $masterPos = $lb->getMasterPos();
 
                $loop = new WaitConditionLoop(
index e299f7e..449c9ff 100644 (file)
@@ -1068,7 +1068,7 @@ class Article implements Page {
                        return false;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $oldestRevisionTimestamp = $dbr->selectField(
                        'revision',
                        'MIN( rev_timestamp )',
@@ -1151,7 +1151,7 @@ class Article implements Page {
 
                if ( !$rc ) {
                        // Don't cache: This can be hit if the page gets accessed very fast after
-                       // its creation / latest upload or in case we have high slave lag. In case
+                       // its creation / latest upload or in case we have high replica DB lag. In case
                        // the revision is too old, we will already return above.
                        return false;
                }
@@ -1705,7 +1705,7 @@ class Article implements Page {
                        // This, as a side-effect, also makes sure that the following query isn't being run for
                        // pages with a larger history, unless the user has the 'bigdelete' right
                        // (and is about to delete this page).
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $revisions = $edits = (int)$dbr->selectField(
                                'revision',
                                'COUNT(rev_page)',
index 1396685..be5535a 100644 (file)
@@ -810,7 +810,7 @@ EOT
         * @return ResultWrapper
         */
        protected function queryImageLinks( $target, $limit ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                return $dbr->select(
                        [ 'imagelinks', 'page' ],
index e1d9f99..d5dfd3d 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 use \MediaWiki\Logger\LoggerFactory;
+use \MediaWiki\MediaWikiServices;
 
 /**
  * Class representing a MediaWiki article and history.
@@ -134,7 +135,7 @@ class WikiPage implements Page, IDBAccessObject {
         *
         * @param int $id Article ID to load
         * @param string|int $from One of the following values:
-        *        - "fromdb" or WikiPage::READ_NORMAL to select from a slave database
+        *        - "fromdb" or WikiPage::READ_NORMAL to select from a replica DB
         *        - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
         *
         * @return WikiPage|null
@@ -146,7 +147,7 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                $from = self::convertSelectType( $from );
-               $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
+               $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_REPLICA );
                $row = $db->selectRow(
                        'page', self::selectFields(), [ 'page_id' => $id ], __METHOD__ );
                if ( !$row ) {
@@ -161,7 +162,7 @@ class WikiPage implements Page, IDBAccessObject {
         * @since 1.20
         * @param object $row Database row containing at least fields returned by selectFields().
         * @param string|int $from Source of $data:
-        *        - "fromdb" or WikiPage::READ_NORMAL: from a slave DB
+        *        - "fromdb" or WikiPage::READ_NORMAL: from a replica DB
         *        - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
         *        - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
         * @return WikiPage
@@ -346,7 +347,7 @@ class WikiPage implements Page, IDBAccessObject {
         *
         * @param object|string|int $from One of the following:
         *   - A DB query result object.
-        *   - "fromdb" or WikiPage::READ_NORMAL to get from a slave DB.
+        *   - "fromdb" or WikiPage::READ_NORMAL to get from a replica DB.
         *   - "fromdbmaster" or WikiPage::READ_LATEST to get from the master DB.
         *   - "forupdate"  or WikiPage::READ_LOCKING to get from the master DB
         *     using SELECT FOR UPDATE.
@@ -365,7 +366,7 @@ class WikiPage implements Page, IDBAccessObject {
                        $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts );
 
                        if ( !$data
-                               && $index == DB_SLAVE
+                               && $index == DB_REPLICA
                                && wfGetLB()->getServerCount() > 1
                                && wfGetLB()->hasOrMadeRecentMasterChanges()
                        ) {
@@ -374,7 +375,7 @@ class WikiPage implements Page, IDBAccessObject {
                                $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts );
                        }
                } else {
-                       // No idea from where the caller got this data, assume slave database.
+                       // No idea from where the caller got this data, assume replica DB.
                        $data = $from;
                        $from = self::READ_NORMAL;
                }
@@ -388,7 +389,7 @@ class WikiPage implements Page, IDBAccessObject {
         * @since 1.20
         * @param object|bool $data DB row containing fields returned by selectFields() or false
         * @param string|int $from One of the following:
-        *        - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
+        *        - "fromdb" or WikiPage::READ_NORMAL if the data comes from a replica DB
         *        - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
         *        - "forupdate"  or WikiPage::READ_LOCKING if the data comes from
         *          the master DB using SELECT FOR UPDATE
@@ -552,9 +553,9 @@ class WikiPage implements Page, IDBAccessObject {
         */
        public function getOldestRevision() {
 
-               // Try using the slave database first, then try the master
+               // Try using the replica DB first, then try the master
                $continue = 2;
-               $db = wfGetDB( DB_SLAVE );
+               $db = wfGetDB( DB_REPLICA );
                $revSelectFields = Revision::selectFields();
 
                $row = null;
@@ -609,7 +610,7 @@ class WikiPage implements Page, IDBAccessObject {
                        $flags = Revision::READ_LOCKING;
                } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
                        // Bug T93976: if page_latest was loaded from the master, fetch the
-                       // revision from there as well, as it may not exist yet on a slave DB.
+                       // revision from there as well, as it may not exist yet on a replica DB.
                        // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
                        $flags = Revision::READ_LATEST;
                } else {
@@ -831,7 +832,7 @@ class WikiPage implements Page, IDBAccessObject {
                                // links.
                                $hasLinks = (bool)count( $editInfo->output->getLinks() );
                        } else {
-                               $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+                               $hasLinks = (bool)wfGetDB( DB_REPLICA )->selectField( 'pagelinks', 1,
                                        [ 'pl_from' => $this->getId() ], __METHOD__ );
                        }
                }
@@ -856,7 +857,7 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                // Query the redirect table
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $row = $dbr->selectRow( 'redirect',
                        [ 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
                        [ 'rd_from' => $this->getId() ],
@@ -989,7 +990,7 @@ class WikiPage implements Page, IDBAccessObject {
        public function getContributors() {
                // @todo FIXME: This is expensive; cache this info somewhere.
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                if ( $dbr->implicitGroupby() ) {
                        $realNameField = 'user_real_name';
@@ -1386,7 +1387,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                $baseRevId = null;
                if ( $edittime && $sectionId !== 'new' ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $rev = Revision::loadFromTimestamp( $dbr, $this->mTitle, $edittime );
                        // Try the master if this thread may have just added it.
                        // This could be abstracted into a Revision method, but we don't want
@@ -2110,7 +2111,7 @@ class WikiPage implements Page, IDBAccessObject {
                                // We get here if vary-revision is set. This means that this page references
                                // itself (such as via self-transclusion). In this case, we need to make sure
                                // that any such self-references refer to the newly-saved revision, and not
-                               // to the previous one, which could otherwise happen due to slave lag.
+                               // to the previous one, which could otherwise happen due to replica DB lag.
                                $oldCallback = $edit->popts->getCurrentRevisionCallback();
                                $edit->popts->setCurrentRevisionCallback(
                                        function ( Title $title, $parser = false ) use ( $revision, &$oldCallback ) {
@@ -2713,14 +2714,14 @@ class WikiPage implements Page, IDBAccessObject {
                $protectDescription = '';
 
                foreach ( array_filter( $limit ) as $action => $restrictions ) {
-                       # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
+                       # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
                        # All possible message keys are listed here for easier grepping:
                        # * restriction-create
                        # * restriction-edit
                        # * restriction-move
                        # * restriction-upload
                        $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
-                       # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
+                       # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
                        # with '' filtered out. All possible message keys are listed below:
                        # * protect-level-autoconfirmed
                        # * protect-level-sysop
@@ -2900,8 +2901,9 @@ class WikiPage implements Page, IDBAccessObject {
                        $bitfield |= Revision::DELETED_COMMENT;
                        $bitfield |= Revision::DELETED_USER;
                        $bitfield |= Revision::DELETED_RESTRICTED;
+                       $deletionFields = [ $dbw->addQuotes( $bitfield ) . ' AS deleted' ];
                } else {
-                       $bitfield = 'rev_deleted';
+                       $deletionFields = [ 'rev_deleted AS deleted' ];
                }
 
                // For now, shunt the revision data into the archive table.
@@ -2912,9 +2914,10 @@ class WikiPage implements Page, IDBAccessObject {
                // the rev_deleted field, which is reserved for this purpose.
 
                // Get all of the page revisions
+               $fields = array_diff( Revision::selectFields(), [ 'rev_deleted' ] );
                $res = $dbw->select(
                        'revision',
-                       Revision::selectFields(),
+                       array_merge( $fields, $deletionFields ),
                        [ 'rev_page' => $id ],
                        __METHOD__,
                        'FOR UPDATE'
@@ -2937,7 +2940,7 @@ class WikiPage implements Page, IDBAccessObject {
                                'ar_flags'      => '',
                                'ar_len'        => $row->rev_len,
                                'ar_page_id'    => $id,
-                               'ar_deleted'    => $bitfield,
+                               'ar_deleted'    => $row->deleted,
                                'ar_sha1'       => $row->rev_sha1,
                        ];
                        if ( $wgContentHandlerUseDB ) {
@@ -3232,9 +3235,12 @@ class WikiPage implements Page, IDBAccessObject {
                        $flags |= EDIT_FORCE_BOT;
                }
 
+               $targetContent = $target->getContent();
+               $changingContentModel = $targetContent->getModel() !== $current->getContentModel();
+
                // Actually store the edit
                $status = $this->doEditContent(
-                       $target->getContent(),
+                       $targetContent,
                        $summary,
                        $flags,
                        $target->getId(),
@@ -3284,6 +3290,22 @@ class WikiPage implements Page, IDBAccessObject {
                        ] ];
                }
 
+               if ( $changingContentModel ) {
+                       // If the content model changed during the rollback,
+                       // make sure it gets logged to Special:Log/contentmodel
+                       $log = new ManualLogEntry( 'contentmodel', 'change' );
+                       $log->setPerformer( $guser );
+                       $log->setTarget( $this->mTitle );
+                       $log->setComment( $summary );
+                       $log->setParameters( [
+                               '4::oldmodel' => $current->getContentModel(),
+                               '5::newmodel' => $targetContent->getModel(),
+                       ] );
+
+                       $logId = $log->insert( $dbw );
+                       $log->publish( $logId );
+               }
+
                $revId = $statusRev->getId();
 
                Hooks::run( 'ArticleRollbackComplete', [ $this, $guser, $target, $current ] );
@@ -3319,9 +3341,11 @@ class WikiPage implements Page, IDBAccessObject {
                $title->purgeSquid();
                $title->deleteTitleProtection();
 
+               MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
+
                if ( $title->getNamespace() == NS_CATEGORY ) {
                        // Load the Category object, which will schedule a job to create
-                       // the category table row if necessary. Checking a slave is ok
+                       // the category table row if necessary. Checking a replica DB is ok
                        // here, in the worst case it'll run an unnecessary recount job on
                        // a category that probably doesn't have many members.
                        Category::newFromTitle( $title )->getID();
@@ -3344,6 +3368,8 @@ class WikiPage implements Page, IDBAccessObject {
                $title->touchLinks();
                $title->purgeSquid();
 
+               MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
+
                // File cache
                HTMLFileCache::clearFileCache( $title );
                InfoAction::invalidateCache( $title );
@@ -3387,6 +3413,8 @@ class WikiPage implements Page, IDBAccessObject {
                // Invalidate the caches of all pages which redirect here
                DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'redirect' ) );
 
+               MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
+
                // Purge CDN for this page only
                $title->purgeSquid();
                // Clear file cache for this page only
@@ -3412,7 +3440,7 @@ class WikiPage implements Page, IDBAccessObject {
                        return TitleArray::newFromResult( new FakeResultWrapper( [] ) );
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select( 'categorylinks',
                        [ 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ],
                        // Have to do that since DatabaseBase::fieldNamesWithAlias treats numeric indexes
@@ -3437,7 +3465,7 @@ class WikiPage implements Page, IDBAccessObject {
                        return [];
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select( [ 'categorylinks', 'page_props', 'page' ],
                        [ 'cl_to' ],
                        [ 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
index a96ca87..395cee5 100644 (file)
@@ -145,8 +145,8 @@ abstract class IndexPager extends ContextSource implements Pager {
                }
 
                $this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' );
-               # Let the subclass set the DB here; otherwise use a slave DB for the current wiki
-               $this->mDb = $this->mDb ?: wfGetDB( DB_SLAVE );
+               # Let the subclass set the DB here; otherwise use a replica DB for the current wiki
+               $this->mDb = $this->mDb ?: wfGetDB( DB_REPLICA );
 
                $index = $this->getIndexField(); // column to sort on
                $extraSort = $this->getExtraSortFields(); // extra columns to sort on for query planning
index b34ac1f..b32f43b 100644 (file)
@@ -289,7 +289,7 @@ class LinkHolderArray {
                $output = $this->parent->getOutput();
                $linkRenderer = $this->parent->getLinkRenderer();
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                # Sort by namespace
                ksort( $this->internals );
@@ -534,7 +534,7 @@ class LinkHolderArray {
 
                if ( !$linkBatch->isEmpty() ) {
                        // construct query
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $fields = array_merge(
                                LinkCache::getSelectFields(),
                                [ 'page_namespace', 'page_title' ]
index b4d9c70..b53920b 100644 (file)
@@ -3458,10 +3458,18 @@ class Parser {
         * @since 1.24
         * @param Title $title
         * @param Parser|bool $parser
-        * @return Revision
+        * @return Revision|bool False if missing
         */
-       public static function statelessFetchRevision( $title, $parser = false ) {
-               return Revision::newFromTitle( $title );
+       public static function statelessFetchRevision( Title $title, $parser = false ) {
+               $pageId = $title->getArticleID();
+               $revId = $title->getLatestRevID();
+
+               $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $pageId, $revId );
+               if ( $rev ) {
+                       $rev->setTitle( $title );
+               }
+
+               return $rev;
        }
 
        /**
@@ -3676,7 +3684,7 @@ class Parser {
         */
        public function fetchScaryTemplateMaybeFromCache( $url ) {
                global $wgTranscludeCacheExpiry;
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
                $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
                                [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
index f2c59d2..5e8db07 100644 (file)
@@ -246,7 +246,7 @@ LUA;
                        } elseif ( $slot === 'QUEUE_WAIT' ) {
                                // This process is now registered as waiting
                                $keys = ( $doWakeup == self::AWAKE_ALL )
-                                       // Wait for an open slot or wake-up signal (preferring the later)
+                                       // Wait for an open slot or wake-up signal (preferring the latter)
                                        ? [ $this->getWakeupListKey(), $this->getSlotListKey() ]
                                        // Just wait for an actual pool slot
                                        : [ $this->getSlotListKey() ];
@@ -292,7 +292,7 @@ LUA;
                local rMaxWorkers,rMaxQueue,rTimeout,rExpiry,rSess,rTime = unpack(ARGV)
                -- Initialize if the "next release" time sorted-set is empty. The slot key
                -- itself is empty if all slots are busy or when nothing is initialized.
-               -- If the list is empty but the set is not, then it is the later case.
+               -- If the list is empty but the set is not, then it is the latter case.
                -- For sanity, if the list exists but not the set, then reset everything.
                if redis.call('exists',kSlotsNextRelease) == 0 then
                        redis.call('del',kSlots)
index 9ce2c5a..ad1ed49 100644 (file)
@@ -109,7 +109,7 @@ class ResourceLoader implements LoggerAwareInterface {
                        // Or else Database*::select() will explode, plus it's cheaper!
                        return;
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $skin = $context->getSkin();
                $lang = $context->getLanguage();
 
@@ -140,6 +140,9 @@ class ResourceLoader implements LoggerAwareInterface {
                        }
                }
 
+               // Batched version of ResourceLoaderWikiModule::getTitleInfo
+               ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $moduleNames );
+
                // Prime in-object cache for message blobs for modules with messages
                $modules = [];
                foreach ( $moduleNames as $name ) {
@@ -610,17 +613,49 @@ class ResourceLoader implements LoggerAwareInterface {
         *
         * @since 1.26
         * @param ResourceLoaderContext $context
-        * @param array $modules List of ResourceLoaderModule objects
+        * @param string[] $modules List of known module names
         * @return string Hash
         */
-       public function getCombinedVersion( ResourceLoaderContext $context, array $modules ) {
-               if ( !$modules ) {
+       public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
+               if ( !$moduleNames ) {
                        return '';
                }
                $hashes = array_map( function ( $module ) use ( $context ) {
                        return $this->getModule( $module )->getVersionHash( $context );
-               }, $modules );
-               return self::makeHash( implode( $hashes ) );
+               }, $moduleNames );
+               return self::makeHash( implode( '', $hashes ) );
+       }
+
+       /**
+        * Get the expected value of the 'version' query parameter.
+        *
+        * This is used by respond() to set a short Cache-Control header for requests with
+        * information newer than the current server has. This avoids pollution of edge caches.
+        * Typically during deployment. (T117587)
+        *
+        * This MUST match return value of `mw.loader#getCombinedVersion()` client-side.
+        *
+        * @since 1.28
+        * @param ResourceLoaderContext $context
+        * @param string[] $modules List of module names
+        * @return string Hash
+        */
+       public function makeVersionQuery( ResourceLoaderContext $context ) {
+               // As of MediaWiki 1.28, the server and client use the same algorithm for combining
+               // version hashes. There is no technical reason for this to be same, and for years the
+               // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
+               // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
+               // query parameter), then this method must continue to match the JS one.
+               $moduleNames = [];
+               foreach ( $context->getModules() as $name ) {
+                       if ( !$this->getModule( $name ) ) {
+                               // If a versioned request contains a missing module, the version is a mismatch
+                               // as the client considered a module (and version) we don't have.
+                               return '';
+                       }
+                       $moduleNames[] = $name;
+               }
+               return $this->getCombinedVersion( $context, $moduleNames );
        }
 
        /**
@@ -759,10 +794,14 @@ class ResourceLoader implements LoggerAwareInterface {
         */
        protected function sendResponseHeaders( ResourceLoaderContext $context, $etag, $errors ) {
                $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
-               // If a version wasn't specified we need a shorter expiry time for updates
-               // to propagate to clients quickly
-               // If there were errors, we also need a shorter expiry time so we can recover quickly
-               if ( is_null( $context->getVersion() ) || $errors ) {
+               // Use a short cache expiry so that updates propagate to clients quickly, if:
+               // - No version specified (shared resources, e.g. stylesheets)
+               // - There were errors (recover quickly)
+               // - Version mismatch (T117587, T47877)
+               if ( is_null( $context->getVersion() )
+                       || $errors
+                       || $context->getVersion() !== $this->makeVersionQuery( $context )
+               ) {
                        $maxage = $rlMaxage['unversioned']['client'];
                        $smaxage = $rlMaxage['unversioned']['server'];
                // If a version was specified we can use a longer expiry time since changing
@@ -857,7 +896,7 @@ class ResourceLoader implements LoggerAwareInterface {
                $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
                if ( !$good ) {
                        try { // RL always hits the DB on file cache miss...
-                               wfGetDB( DB_SLAVE );
+                               wfGetDB( DB_REPLICA );
                        } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
                                $good = $fileCache->isCacheGood(); // cache existence check
                        }
index dc70af4..5729218 100644 (file)
@@ -429,6 +429,7 @@ class ResourceLoaderClientHtml {
                foreach ( $sortedModules as $source => $groups ) {
                        foreach ( $groups as $group => $grpModules ) {
                                $context = self::makeContext( $mainContext, $group, $only, $extraQuery );
+                               $context->setModules( array_keys( $grpModules ) );
 
                                if ( $group === 'private' ) {
                                        // Decide whether to use style or script element
@@ -456,11 +457,10 @@ class ResourceLoaderClientHtml {
                                // This should NOT be done for the site group (bug 27564) because anons get that too
                                // and we shouldn't be putting timestamps in CDN-cached HTML
                                if ( $group === 'user' ) {
-                                       $version = $rl->getCombinedVersion( $context, array_keys( $grpModules ) );
-                                       $context->setVersion( $version );
+                                       // Must setModules() before makeVersionQuery()
+                                       $context->setVersion( $rl->makeVersionQuery( $context ) );
                                }
 
-                               $context->setModules( array_keys( $grpModules ) );
                                $url = $rl->createLoaderURL( $source, $context, $extraQuery );
 
                                // Decide whether to use 'style' or 'script' element
index de89fc7..43cf78b 100644 (file)
@@ -417,7 +417,7 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
 
                // Try in-object cache first
                if ( !isset( $this->fileDeps[$vary] ) ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $deps = $dbr->selectField( 'module_deps',
                                'md_deps',
                                [
index eb9788c..8970620 100644 (file)
@@ -311,19 +311,14 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
         */
        public static function getStartupModulesUrl( ResourceLoaderContext $context ) {
                $rl = $context->getResourceLoader();
-               $moduleNames = self::getStartupModules();
 
-               $query = [
-                       'modules' => ResourceLoader::makePackedModulesString( $moduleNames ),
-                       'only' => 'scripts',
-                       'lang' => $context->getLanguage(),
-                       'skin' => $context->getSkin(),
-                       'debug' => $context->getDebug() ? 'true' : 'false',
-                       'version' => $rl->getCombinedVersion( $context, $moduleNames ),
-               ];
-               // Ensure uniform query order
-               ksort( $query );
-               return wfAppendQuery( wfScript( 'load' ), $query );
+               $derivative = new DerivativeResourceLoaderContext( $context );
+               $derivative->setModules( self::getStartupModules() );
+               $derivative->setOnly( 'scripts' );
+               // Must setModules() before makeVersionQuery()
+               $derivative->setVersion( $rl->makeVersionQuery( $derivative ) );
+
+               return $rl->createLoaderURL( 'local', $derivative );
        }
 
        /**
index 82051b1..5580306 100644 (file)
@@ -130,7 +130,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
        /**
         * Get the Database object used in getTitleInfo().
         *
-        * Defaults to the local slave DB. Subclasses may want to override this to return a foreign
+        * Defaults to the local replica DB. Subclasses may want to override this to return a foreign
         * database object, or null if getTitleInfo() shouldn't access the database.
         *
         * NOTE: This ONLY works for getTitleInfo() and isKnownEmpty(), NOT FOR ANYTHING ELSE.
@@ -139,7 +139,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
         * @return IDatabase|null
         */
        protected function getDB() {
-               return wfGetDB( DB_SLAVE );
+               return wfGetDB( DB_REPLICA );
        }
 
        /**
@@ -246,7 +246,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                $summary = parent::getDefinitionSummary( $context );
                $summary[] = [
                        'pages' => $this->getPages( $context ),
-                       // Includes SHA1 of content
+                       // Includes meta data of current revisions
                        'titleInfo' => $this->getTitleInfo( $context ),
                ];
                return $summary;
@@ -262,7 +262,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                // For user modules, don't needlessly load if there are no non-empty pages
                if ( $this->getGroup() === 'user' ) {
                        foreach ( $revisions as $revision ) {
-                               if ( $revision['rev_len'] > 0 ) {
+                               if ( $revision['page_len'] > 0 ) {
                                        // At least one non-empty page, module should be loaded
                                        return false;
                                }
@@ -276,10 +276,14 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                return count( $revisions ) === 0;
        }
 
+       private function setTitleInfo( $key, array $titleInfo ) {
+               $this->titleInfo[$key] = $titleInfo;
+       }
+
        /**
         * Get the information about the wiki pages for a given context.
         * @param ResourceLoaderContext $context
-        * @return array Keyed by page name. Contains arrays with 'rev_len' and 'rev_sha1' keys
+        * @return array Keyed by page name
         */
        protected function getTitleInfo( ResourceLoaderContext $context ) {
                $dbr = $this->getDB();
@@ -288,37 +292,77 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                        return [];
                }
 
-               $pages = $this->getPages( $context );
-               $key = implode( '|', array_keys( $pages ) );
+               $pageNames = array_keys( $this->getPages( $context ) );
+               sort( $pageNames );
+               $key = implode( '|', $pageNames );
                if ( !isset( $this->titleInfo[$key] ) ) {
-                       $this->titleInfo[$key] = [];
-                       $batch = new LinkBatch;
-                       foreach ( $pages as $titleText => $options ) {
-                               $batch->addObj( Title::newFromText( $titleText ) );
+                       $this->titleInfo[$key] = self::fetchTitleInfo( $dbr, $pageNames, __METHOD__ );
+               }
+               return $this->titleInfo[$key];
+       }
+
+       private static function fetchTitleInfo( IDatabase $db, array $pages, $fname = __METHOD__ ) {
+               $titleInfo = [];
+               $batch = new LinkBatch;
+               foreach ( $pages as $titleText ) {
+                       $batch->addObj( Title::newFromText( $titleText ) );
+               }
+               if ( !$batch->isEmpty() ) {
+                       $res = $db->select( 'page',
+                               // Include page_touched to allow purging if cache is poisoned (T117587, T113916)
+                               [ 'page_namespace', 'page_title', 'page_touched', 'page_len', 'page_latest' ],
+                               $batch->constructSet( 'page', $db ),
+                               $fname
+                       );
+                       foreach ( $res as $row ) {
+                               // Avoid including ids or timestamps of revision/page tables so
+                               // that versions are not wasted
+                               $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+                               $titleInfo[$title->getPrefixedText()] = [
+                                       'page_len' => $row->page_len,
+                                       'page_latest' => $row->page_latest,
+                                       'page_touched' => $row->page_touched,
+                               ];
                        }
+               }
+               return $titleInfo;
+       }
 
-                       if ( !$batch->isEmpty() ) {
-                               $res = $dbr->select( [ 'page', 'revision' ],
-                                       // Include page_touched to allow purging if cache is poisoned (T117587, T113916)
-                                       [ 'page_namespace', 'page_title', 'page_touched', 'rev_len', 'rev_sha1' ],
-                                       $batch->constructSet( 'page', $dbr ),
-                                       __METHOD__,
-                                       [],
-                                       [ 'revision' => [ 'INNER JOIN', [ 'page_latest=rev_id' ] ] ]
-                               );
-                               foreach ( $res as $row ) {
-                                       // Avoid including ids or timestamps of revision/page tables so
-                                       // that versions are not wasted
-                                       $title = Title::makeTitle( $row->page_namespace, $row->page_title );
-                                       $this->titleInfo[$key][$title->getPrefixedText()] = [
-                                               'rev_len' => $row->rev_len,
-                                               'rev_sha1' => $row->rev_sha1,
-                                               'page_touched' => $row->page_touched,
-                                       ];
+       /**
+        * @since 1.28
+        * @param ResourceLoaderContext $context
+        * @param IDatabase $db
+        * @param string[] $modules
+        */
+       public static function preloadTitleInfo(
+               ResourceLoaderContext $context, IDatabase $db, array $moduleNames
+       ) {
+               $rl = $context->getResourceLoader();
+               // getDB() can be overridden to point to a foreign database.
+               // For now, only preload local. In the future, we could preload by wikiID.
+               $allPages = [];
+               $wikiModules = [];
+               foreach ( $moduleNames as $name ) {
+                       $module = $rl->getModule( $name );
+                       if ( $module instanceof self ) {
+                               $mDB = $module->getDB();
+                               // Subclasses may disable getDB and implement getTitleInfo differently
+                               if ( $mDB && $mDB->getWikiID() === $db->getWikiID() ) {
+                                       $wikiModules[] = $module;
+                                       $allPages += $module->getPages( $context );
                                }
                        }
                }
-               return $this->titleInfo[$key];
+               $allInfo = self::fetchTitleInfo( $db, array_keys( $allPages ), __METHOD__ );
+               foreach ( $wikiModules as $module ) {
+                       $pages = $module->getPages( $context );
+                       $info = array_intersect_key( $allInfo, $pages );
+                       $pageNames = array_keys( $pages );
+                       sort( $pageNames );
+                       $key = implode( '|', $pageNames );
+                       $module->setTitleInfo( $key, $info );
+               }
+               return $allInfo;
        }
 
        /**
index 07fe4a1..ff1d2ed 100644 (file)
@@ -40,7 +40,7 @@ class RevDelLogList extends RevDelList {
        }
 
        public static function suggestTarget( $target, array $ids ) {
-               $result = wfGetDB( DB_SLAVE )->select( 'logging',
+               $result = wfGetDB( DB_REPLICA )->select( 'logging',
                        'log_type',
                        [ 'log_id' => $ids ],
                        __METHOD__,
index 3486645..1d08dbe 100644 (file)
@@ -60,7 +60,7 @@ class RevDelRevisionList extends RevDelList {
        public function doQuery( $db ) {
                $ids = array_map( 'intval', $this->ids );
                $queryInfo = [
-                       'tables' => [ 'revision', 'user' ],
+                       'tables' => [ 'revision', 'page', 'user' ],
                        'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
                        'conds' => [
                                'rev_page' => $this->title->getArticleID(),
index eea0c28..b834c15 100644 (file)
@@ -213,7 +213,7 @@ class RevisionDeleter {
         * @return bool|mixed
         */
        public static function checkRevisionExistence( $title, $revid ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $exists = $dbr->selectField( 'revision', '1',
                                [ 'rev_id' => $revid ], __METHOD__ );
 
index 5b18b7c..38c60d0 100644 (file)
@@ -40,7 +40,7 @@ class SearchDatabase extends SearchEngine {
                if ( $db ) {
                        $this->db = $db;
                } else {
-                       $this->db = wfGetDB( DB_SLAVE );
+                       $this->db = wfGetDB( DB_REPLICA );
                }
        }
 
index 67f500c..e30869e 100644 (file)
@@ -32,7 +32,7 @@ class SearchEngineFactory {
                } elseif ( $configType !== null ) {
                        $class = $configType;
                } else {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $class = $dbr->getSearchEngine();
                }
 
index 2bd1955..dd41a6e 100644 (file)
@@ -34,10 +34,11 @@ class SearchHighlighter {
        }
 
        /**
-        * Default implementation of wikitext highlighting
+        * Wikitext highlighting when $wgAdvancedSearchHighlighting = true
         *
         * @param string $text
-        * @param array $terms Terms to highlight (unescaped)
+        * @param array $terms Terms to highlight (not html escaped but
+        *   regex escaped via SearchDatabase::regexTerm())
         * @param int $contextlines
         * @param int $contextchars
         * @return string
@@ -145,7 +146,6 @@ class SearchHighlighter {
                }
                $anyterm = implode( '|', $terms );
                $phrase = implode( "$wgSearchHighlightBoundaries+", $terms );
-
                // @todo FIXME: A hack to scale contextchars, a correct solution
                // would be to have contextchars actually be char and not byte
                // length, and do proper utf-8 substrings and lengths everywhere,
@@ -485,8 +485,10 @@ class SearchHighlighter {
         * Simple & fast snippet extraction, but gives completely unrelevant
         * snippets
         *
+        * Used when $wgAdvancedSearchHighlighting is false.
+        *
         * @param string $text
-        * @param array $terms
+        * @param array $terms Escaped for regex by SearchDatabase::regexTerm()
         * @param int $contextlines
         * @param int $contextchars
         * @return string
index 7e82378..36cbbaa 100644 (file)
@@ -437,7 +437,7 @@ class SearchMySQL extends SearchDatabase {
                if ( is_null( self::$mMinSearchLength ) ) {
                        $sql = "SHOW GLOBAL VARIABLES LIKE 'ft\\_min\\_word\\_len'";
 
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $result = $dbr->query( $sql, __METHOD__ );
                        $row = $result->fetchObject();
                        $result->free();
index 974789f..432d5ce 100644 (file)
@@ -73,7 +73,7 @@ class DBSiteStore implements SiteStore {
        protected function loadSites() {
                $this->sites = new SiteList();
 
-               $dbr = $this->dbLoadBalancer->getConnection( DB_SLAVE );
+               $dbr = $this->dbLoadBalancer->getConnection( DB_REPLICA );
 
                $res = $dbr->select(
                        'sites',
index d473251..6b4acfa 100644 (file)
@@ -846,7 +846,7 @@ abstract class Skin extends ContextSource {
                        $s = '';
                }
 
-               if ( wfGetLB()->getLaggedSlaveMode() ) {
+               if ( wfGetLB()->getLaggedReplicaMode() ) {
                        $s .= ' <strong>' . $this->msg( 'laggedslavemode' )->parse() . '</strong>';
                }
 
index b4be461..f185789 100644 (file)
@@ -17,6 +17,8 @@
  *
  * @file
  */
+
+use MediaWiki\Auth\AuthManager;
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -244,7 +246,7 @@ class SkinTemplate extends Skin {
                $out = $this->getOutput();
 
                $this->initPage( $out );
-               $tpl = $this->prepareQuickTemplate( $out );
+               $tpl = $this->prepareQuickTemplate();
                // execute template
                $res = $tpl->execute();
 
@@ -574,6 +576,7 @@ class SkinTemplate extends Skin {
                $title = $this->getTitle();
                $request = $this->getRequest();
                $pageurl = $title->getLocalURL();
+               $authManager = AuthManager::singleton();
 
                /* set up the default links for the personal toolbar */
                $personal_urls = [];
@@ -652,17 +655,25 @@ class SkinTemplate extends Skin {
                                'href' => $href,
                                'active' => $active
                        ];
-                       $personal_urls['logout'] = [
-                               'text' => $this->msg( 'pt-userlogout' )->text(),
-                               'href' => self::makeSpecialUrl( 'Userlogout',
-                                       // userlogout link must always contain an & character, otherwise we might not be able
-                                       // to detect a buggy precaching proxy (bug 17790)
-                                       $title->isSpecial( 'Preferences' ) ? 'noreturnto' : $returnto
-                               ),
-                               'active' => false
-                       ];
+
+                       // if we can't set the user, we can't unset it either
+                       if ( $request->getSession()->canSetUser() ) {
+                               $personal_urls['logout'] = [
+                                       'text' => $this->msg( 'pt-userlogout' )->text(),
+                                       'href' => self::makeSpecialUrl( 'Userlogout',
+                                               // userlogout link must always contain an & character, otherwise we might not be able
+                                               // to detect a buggy precaching proxy (bug 17790)
+                                               $title->isSpecial( 'Preferences' ) ? 'noreturnto' : $returnto ),
+                                       'active' => false
+                               ];
+                       }
                } else {
                        $useCombinedLoginLink = $this->useCombinedLoginLink();
+                       if ( !$authManager->canCreateAccounts() || !$authManager->canAuthenticateNow() ) {
+                               // don't show combined login/signup link if one of those is actually not available
+                               $useCombinedLoginLink = false;
+                       }
+
                        $loginlink = $this->getUser()->isAllowed( 'createaccount' ) && $useCombinedLoginLink
                                ? 'nav-login-createaccount'
                                : 'pt-login';
@@ -699,11 +710,17 @@ class SkinTemplate extends Skin {
                                ];
                        }
 
-                       if ( $this->getUser()->isAllowed( 'createaccount' ) && !$useCombinedLoginLink ) {
+                       if (
+                               $authManager->canCreateAccounts()
+                               && $this->getUser()->isAllowed( 'createaccount' )
+                               && !$useCombinedLoginLink
+                       ) {
                                $personal_urls['createaccount'] = $createaccount_url;
                        }
 
-                       $personal_urls['login'] = $login_url;
+                       if ( $authManager->canAuthenticateNow() ) {
+                               $personal_urls['login'] = $login_url;
+                       }
                }
 
                Hooks::run( 'PersonalUrls', [ &$personal_urls, &$title, $this ] );
index 60f1dd8..01782f3 100644 (file)
@@ -340,7 +340,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * @return IDatabase
         */
        protected function getDB() {
-               return wfGetDB( DB_SLAVE );
+               return wfGetDB( DB_REPLICA );
        }
 
        /**
index c3d43df..9d17e7d 100644 (file)
@@ -223,11 +223,16 @@ abstract class LoginSignupSpecialPage extends AuthManagerSpecialPage {
                $this->setHeaders();
                $this->checkPermissions();
 
-               // Make sure it's possible to log in
-               if ( !$this->isSignup() && !$session->canSetUser() ) {
-                       throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
+               // Make sure the system configuration allows log in / sign up
+               if ( !$this->isSignup() && !$authManager->canAuthenticateNow() ) {
+                       if ( !$session->canSetUser() ) {
+                               throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
                                        $session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
                                ] );
+                       }
+                       throw new ErrorPageError( 'cannotlogin-title', 'cannotlogin-text' );
+               } elseif ( $this->isSignup() && !$authManager->canCreateAccounts() ) {
+                       throw new ErrorPageError( 'cannotcreateaccount-title', 'cannotcreateaccount-text' );
                }
 
                /*
index 1beac43..afbc581 100644 (file)
@@ -376,7 +376,7 @@ abstract class QueryPage extends SpecialPage {
         * @return IDatabase
         */
        function getRecacheDB() {
-               return wfGetDB( DB_SLAVE, [ $this->getName(), 'QueryPage::recache', 'vslow' ] );
+               return wfGetDB( DB_REPLICA, [ $this->getName(), 'QueryPage::recache', 'vslow' ] );
        }
 
        /**
@@ -453,7 +453,7 @@ abstract class QueryPage extends SpecialPage {
         * @since 1.18
         */
        public function fetchFromCache( $limit, $offset = false ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $options = [];
                if ( $limit !== false ) {
                        $options['LIMIT'] = intval( $limit );
@@ -477,7 +477,7 @@ abstract class QueryPage extends SpecialPage {
 
        public function getCachedTimestamp() {
                if ( is_null( $this->cachedTimestamp ) ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $fname = get_class( $this ) . '::getCachedTimestamp';
                        $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp',
                                [ 'qci_type' => $this->getName() ], $fname );
index c697ca7..2da441b 100644 (file)
@@ -62,7 +62,7 @@ class SpecialActiveUsers extends SpecialPage {
 
                // Mention the level of cache staleness...
                $cacheText = '';
-               $dbr = wfGetDB( DB_SLAVE, 'recentchanges' );
+               $dbr = wfGetDB( DB_REPLICA, 'recentchanges' );
                $rcMax = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
                if ( $rcMax ) {
                        $cTime = $dbr->selectField( 'querycache_info',
index b55f0b4..4a2a619 100644 (file)
@@ -181,7 +181,7 @@ class SpecialAllPages extends IncludableSpecialPage {
                        list( $namespace, $fromKey, $from ) = $fromList;
                        list( , $toKey, $to ) = $toList;
 
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $filterConds = [ 'page_namespace' => $namespace ];
                        if ( $hideredirects ) {
                                $filterConds['page_is_redirect'] = 0;
index de58762..c4fb316 100644 (file)
@@ -136,7 +136,7 @@ class SpecialBlockList extends SpecialPage {
                                case Block::TYPE_IP:
                                case Block::TYPE_RANGE:
                                        list( $start, $end ) = IP::parseRange( $target );
-                                       $conds[] = wfGetDB( DB_SLAVE )->makeList(
+                                       $conds[] = wfGetDB( DB_REPLICA )->makeList(
                                                [
                                                        'ipb_address' => $target,
                                                        Block::getRangeCond( $start, $end )
index 7e330aa..61ab642 100644 (file)
@@ -163,7 +163,7 @@ class SpecialBotPasswords extends FormSpecialPage {
 
                } else {
                        $linkRenderer = $this->getLinkRenderer();
-                       $dbr = BotPassword::getDB( DB_SLAVE );
+                       $dbr = BotPassword::getDB( DB_REPLICA );
                        $res = $dbr->select(
                                'bot_passwords',
                                [ 'bp_app_id' ],
index 4c3fbe5..b9b2051 100644 (file)
@@ -49,7 +49,7 @@ class BrokenRedirectsPage extends QueryPage {
        }
 
        public function getQueryInfo() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                return [
                        'tables' => [
index ccbb275..b37c475 100644 (file)
@@ -191,6 +191,12 @@ class SpecialChangeContentModel extends FormSpecialPage {
                        // Page doesn't exist, create an empty content object
                        $newContent = ContentHandler::getForModelID( $data['model'] )->makeEmptyContent();
                }
+
+               // All other checks have passed, let's check rate limits
+               if ( $user->pingLimiter( 'editcontentmodel' ) ) {
+                       throw new ThrottledError();
+               }
+
                $flags = $this->oldRevision ? EDIT_UPDATE : EDIT_NEW;
                $flags |= EDIT_INTERNAL;
                if ( $user->isAllowed( 'bot' ) ) {
index 6aeb2c3..68289a7 100644 (file)
@@ -203,7 +203,7 @@ class SpecialContributions extends IncludableSpecialPage {
                        if ( !$pager->getNumRows() ) {
                                $out->addWikiMsg( 'nocontribs', $target );
                        } else {
-                               # Show a message about slave lag, if applicable
+                               # Show a message about replica DB lag, if applicable
                                $lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
                                if ( $lag > 0 ) {
                                        $out->showLagWarning( $lag );
index 8e168b2..ad12046 100644 (file)
@@ -98,7 +98,7 @@ class DeletedContributionsPage extends SpecialPage {
                        return;
                }
 
-               # Show a message about slave lag, if applicable
+               # Show a message about replica DB lag, if applicable
                $lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
                if ( $lag > 0 ) {
                        $out->showLagWarning( $lag );
@@ -135,7 +135,7 @@ class DeletedContributionsPage extends SpecialPage {
                if ( $userObj->isAnon() ) {
                        $user = htmlspecialchars( $userObj->getName() );
                } else {
-                       $user = $linkRenderer->makeKnownLink( $userObj->getUserPage(), $userObj->getName() );
+                       $user = $linkRenderer->makeLink( $userObj->getUserPage(), $userObj->getName() );
                }
                $links = '';
                $nt = $userObj->getUserPage();
index 7b00064..0cec9d0 100644 (file)
@@ -50,7 +50,7 @@ class DoubleRedirectsPage extends QueryPage {
 
        function reallyGetQueryInfo( $namespace = null, $title = null ) {
                $limitToTitle = !( $namespace === null && $title === null );
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $retval = [
                        'tables' => [
                                'ra' => 'redirect',
@@ -121,7 +121,7 @@ class DoubleRedirectsPage extends QueryPage {
                // get a little more detail about each individual entry quickly
                // using the filter of reallyGetQueryInfo.
                if ( $result && !isset( $result->nsb ) ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $qi = $this->reallyGetQueryInfo(
                                $result->namespace,
                                $result->title
index fe97739..bf535a6 100644 (file)
@@ -370,12 +370,12 @@ class SpecialExport extends SpecialPage {
                /* Ok, let's get to it... */
                if ( $history == WikiExporter::CURRENT ) {
                        $lb = false;
-                       $db = wfGetDB( DB_SLAVE );
+                       $db = wfGetDB( DB_REPLICA );
                        $buffer = WikiExporter::BUFFER;
                } else {
                        // Use an unbuffered query; histories may be very long!
                        $lb = wfGetLBFactory()->newMainLB();
-                       $db = $lb->getConnection( DB_SLAVE );
+                       $db = $lb->getConnection( DB_REPLICA );
                        $buffer = WikiExporter::STREAM;
 
                        // This might take a while... :D
@@ -426,7 +426,7 @@ class SpecialExport extends SpecialPage {
 
                $name = $title->getDBkey();
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select(
                        [ 'page', 'categorylinks' ],
                        [ 'page_namespace', 'page_title' ],
@@ -459,7 +459,7 @@ class SpecialExport extends SpecialPage {
 
                $maxPages = $this->getConfig()->get( 'ExportPagelistLimit' );
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select(
                        'page',
                        [ 'page_namespace', 'page_title' ],
@@ -556,7 +556,7 @@ class SpecialExport extends SpecialPage {
         * @return array
         */
        private function getLinks( $inputPages, $pageSet, $table, $fields, $join ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                foreach ( $inputPages as $page ) {
                        $title = Title::newFromText( $page );
index d4886f0..307d6c3 100644 (file)
@@ -153,7 +153,7 @@ class LinkSearchPage extends QueryPage {
         */
        static function mungeQuery( $query, $prot ) {
                $field = 'el_index';
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                if ( $query === '*' && $prot !== '' ) {
                        // Allow queries like 'ftp://*' to find all ftp links
@@ -185,7 +185,7 @@ class LinkSearchPage extends QueryPage {
        }
 
        public function getQueryInfo() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                // strip everything past first wildcard, so that
                // index-based-only lookup would be done
                list( $this->mungedQuery, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
index e51e8b5..ec87716 100644 (file)
@@ -62,7 +62,7 @@ class MediaStatisticsPage extends QueryPage {
         * Special:BrokenRedirects also rely on this.
         */
        public function getQueryInfo() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $fakeTitle = $dbr->buildConcat( [
                        'img_media_type',
                        $dbr->addQuotes( ';' ),
index f0b463a..3a12cf3 100644 (file)
@@ -223,7 +223,7 @@ class MovePageForm extends UnlistedSpecialPage {
                        ( $oldTalk->exists()
                                || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
                        $hasRedirects = $dbr->selectField( 'redirect', '1',
                                [
index 327ddda..706a1d7 100644 (file)
@@ -174,7 +174,7 @@ class SpecialPagesWithProp extends QueryPage {
                        $opts['OFFSET'] = $offset;
                }
 
-               $res = wfGetDB( DB_SLAVE )->select(
+               $res = wfGetDB( DB_REPLICA )->select(
                        'page_props',
                        'pp_propname',
                        '',
index 87a5b27..5e3e430 100644 (file)
@@ -181,7 +181,7 @@ class SpecialPrefixindex extends SpecialAllPages {
 
                        # ## @todo FIXME: Should complain if $fromNs != $namespace
 
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
 
                        $conds = [
                                'page_namespace' => $namespace,
index 342509c..5bdae15 100644 (file)
@@ -558,7 +558,7 @@ class ProtectedPagesPager extends TablePager {
                        'join_conds' => [
                                'log_search' => [
                                        'LEFT JOIN', [
-                                               'ls_field' => 'pr_id', 'ls_value = pr_id'
+                                               'ls_field' => 'pr_id', 'ls_value = ' . $this->mDb->buildStringCast( 'pr_id' )
                                        ]
                                ],
                                'logging' => [
index a5e538f..fc924a4 100644 (file)
@@ -223,7 +223,7 @@ class SpecialRandomInCategory extends FormSpecialPage {
                        ]
                ];
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $minClTime = $this->getTimestampOffset( $rand );
                if ( $minClTime ) {
                        $qi['conds'][] = 'cl_timestamp ' . $op . ' ' .
@@ -264,7 +264,7 @@ class SpecialRandomInCategory extends FormSpecialPage {
         * @throws MWException If category has no entries.
         */
        protected function getMinAndMaxForCat( Title $category ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->selectRow(
                        'categorylinks',
                        [
@@ -294,7 +294,7 @@ class SpecialRandomInCategory extends FormSpecialPage {
         * @return array Info for the title selected.
         */
        private function selectRandomPageFromDB( $rand, $offset, $up, $fname = __METHOD__ ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $query = $this->getQueryInfo( $rand, $offset, $up );
                $res = $dbr->select(
index d7835d1..e3b567d 100644 (file)
@@ -159,7 +159,7 @@ class RandomPage extends SpecialPage {
        }
 
        private function selectRandomPageFromDB( $randstr, $fname = __METHOD__ ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $query = $this->getQueryInfo( $randstr );
                $res = $dbr->select(
index 31a290d..0df8423 100644 (file)
@@ -28,7 +28,7 @@ class SpecialRandomrootpage extends RandomPage {
 
        public function __construct() {
                parent::__construct( 'Randomrootpage' );
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $this->extra[] = 'page_title NOT ' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
        }
 
index d4fb72c..8aff690 100644 (file)
@@ -281,7 +281,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
        }
 
        protected function getDB() {
-               return wfGetDB( DB_SLAVE, 'recentchanges' );
+               return wfGetDB( DB_REPLICA, 'recentchanges' );
        }
 
        public function outputFeedLinks() {
index 57a3d92..aab0f6d 100644 (file)
@@ -74,7 +74,7 @@ class SpecialRecentChangesLinked extends SpecialRecentChanges {
                 * expects only one result set so we use UNION instead.
                 */
 
-               $dbr = wfGetDB( DB_SLAVE, 'recentchangeslinked' );
+               $dbr = wfGetDB( DB_REPLICA, 'recentchangeslinked' );
                $id = $title->getArticleID();
                $ns = $title->getNamespace();
                $dbkey = $title->getDBkey();
index 80dc797..1d1df6a 100644 (file)
@@ -182,7 +182,7 @@ class SpecialRedirect extends FormSpecialPage {
                        'log_user_text',
                ];
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Gets the nested SQL statement which
                // returns timestamp of the log with the given log ID
index 47bed62..5e5ed25 100644 (file)
@@ -110,7 +110,7 @@ class SpecialTags extends SpecialPage {
                        // continuing with this, as the user is just going to end up getting sent
                        // somewhere else. Additionally, if we keep going here, we end up
                        // populating the memcache of tag data (see ChangeTags::listDefinedTags)
-                       // with out-of-date data from the slave, because the slave hasn't caught
+                       // with out-of-date data from the replica DB, because the replica DB hasn't caught
                        // up to the fact that a new tag has been created as part of an implicit,
                        // as yet uncommitted transaction on master.
                        if ( $out->getRedirect() !== '' ) {
index 65f0680..91753a9 100644 (file)
@@ -63,7 +63,7 @@ class PageArchive {
         * @return ResultWrapper
         */
        public static function listAllPages() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                return self::listPages( $dbr, '' );
        }
@@ -77,7 +77,7 @@ class PageArchive {
         * @return ResultWrapper
         */
        public static function listPagesByPrefix( $prefix ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $title = Title::newFromText( $prefix );
                if ( $title ) {
@@ -127,7 +127,7 @@ class PageArchive {
         * @return ResultWrapper
         */
        function listRevisions() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $tables = [ 'archive' ];
 
@@ -179,7 +179,7 @@ class PageArchive {
                        return null;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                return $dbr->select(
                        'filearchive',
                        ArchivedFile::selectFields(),
@@ -197,7 +197,7 @@ class PageArchive {
         * @return Revision|null
         */
        function getRevision( $timestamp ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $fields = [
                        'ar_rev_id',
@@ -244,7 +244,7 @@ class PageArchive {
         * @return Revision|null Null when there is no previous revision
         */
        function getPreviousRevision( $timestamp ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Check the previous deleted revision...
                $row = $dbr->selectRow( 'archive',
@@ -300,7 +300,7 @@ class PageArchive {
                }
 
                // New-style: keyed to the text storage backend.
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $text = $dbr->selectRow( 'text',
                        [ 'old_text', 'old_flags' ],
                        [ 'old_id' => $row->ar_text_id ],
@@ -318,7 +318,7 @@ class PageArchive {
         * @return string|null
         */
        function getLastRevisionText() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $row = $dbr->selectRow( 'archive',
                        [ 'ar_text', 'ar_flags', 'ar_text_id' ],
                        [ 'ar_namespace' => $this->title->getNamespace(),
@@ -339,7 +339,7 @@ class PageArchive {
         * @return bool
         */
        function isDeleted() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
                        [ 'ar_namespace' => $this->title->getNamespace(),
                                'ar_title' => $this->title->getDBkey() ],
@@ -1247,7 +1247,7 @@ class SpecialUndelete extends SpecialPage {
 
                $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
 
-               $tags = wfGetDB( DB_SLAVE )->selectField(
+               $tags = wfGetDB( DB_REPLICA )->selectField(
                        'tag_summary',
                        'ts_tags',
                        [ 'ts_rev_id' => $rev->getId() ],
index 6ca1100..4583305 100644 (file)
@@ -394,6 +394,18 @@ class SpecialUpload extends SpecialPage {
                        }
                        if ( $warning == 'exists' ) {
                                $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
+                       } elseif ( $warning == 'no-change' ) {
+                               $file = $args;
+                               $filename = $file->getTitle()->getPrefixedText();
+                               $msg = "\t<li>" . wfMessage( 'fileexists-no-change', $filename )->parse() . "</li>\n";
+                       } elseif ( $warning == 'duplicate-version' ) {
+                               $file = $args[0];
+                               $count = count( $args );
+                               $filename = $file->getTitle()->getPrefixedText();
+                               $message = wfMessage( 'fileexists-duplicate-version' )
+                                       ->params( $filename )
+                                       ->numParams( $count );
+                               $msg = "\t<li>" . $message->parse() . "</li>\n";
                        } elseif ( $warning == 'was-deleted' ) {
                                # If the file existed before and was deleted, warn the user of this
                                $ltitle = SpecialPage::getTitleFor( 'Log' );
@@ -763,31 +775,31 @@ class SpecialUpload extends SpecialPage {
 
                $file = $exists['file'];
                $filename = $file->getTitle()->getPrefixedText();
-               $warning = '';
+               $warnMsg = null;
 
                if ( $exists['warning'] == 'exists' ) {
                        // Exact match
-                       $warning = wfMessage( 'fileexists', $filename )->parse();
+                       $warnMsg = wfMessage( 'fileexists', $filename );
                } elseif ( $exists['warning'] == 'page-exists' ) {
                        // Page exists but file does not
-                       $warning = wfMessage( 'filepageexists', $filename )->parse();
+                       $warnMsg = wfMessage( 'filepageexists', $filename );
                } elseif ( $exists['warning'] == 'exists-normalized' ) {
-                       $warning = wfMessage( 'fileexists-extension', $filename,
-                               $exists['normalizedFile']->getTitle()->getPrefixedText() )->parse();
+                       $warnMsg = wfMessage( 'fileexists-extension', $filename,
+                               $exists['normalizedFile']->getTitle()->getPrefixedText() );
                } elseif ( $exists['warning'] == 'thumb' ) {
                        // Swapped argument order compared with other messages for backwards compatibility
-                       $warning = wfMessage( 'fileexists-thumbnail-yes',
-                               $exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse();
+                       $warnMsg = wfMessage( 'fileexists-thumbnail-yes',
+                               $exists['thumbFile']->getTitle()->getPrefixedText(), $filename );
                } elseif ( $exists['warning'] == 'thumb-name' ) {
                        // Image w/o '180px-' does not exists, but we do not like these filenames
                        $name = $file->getName();
                        $badPart = substr( $name, 0, strpos( $name, '-' ) + 1 );
-                       $warning = wfMessage( 'file-thumbnail-no', $badPart )->parse();
+                       $warnMsg = wfMessage( 'file-thumbnail-no', $badPart );
                } elseif ( $exists['warning'] == 'bad-prefix' ) {
-                       $warning = wfMessage( 'filename-bad-prefix', $exists['prefix'] )->parse();
+                       $warnMsg = wfMessage( 'filename-bad-prefix', $exists['prefix'] );
                }
 
-               return $warning;
+               return $warnMsg ? $warnMsg->title( $file->getTitle() )->parse() : '';
        }
 
        /**
index 1412324..9573f33 100644 (file)
@@ -181,7 +181,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
         * Scale a file (probably with a locally installed imagemagick, or similar)
         * and output it to STDOUT.
         * @param File $file
-        * @param array $params Scaling parameters ( e.g. array( width => '50' ) );
+        * @param array $params Scaling parameters ( e.g. [ width => '50' ] );
         * @param int $flags Scaling flags ( see File:: constants )
         * @throws MWException|UploadStashFileNotFoundException
         * @return bool Success
@@ -227,7 +227,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
         * client to cache it forever.
         *
         * @param File $file
-        * @param array $params Scaling parameters ( e.g. array( width => '50' ) );
+        * @param array $params Scaling parameters ( e.g. [ width => '50' ] );
         * @param int $flags Scaling flags ( see File:: constants )
         * @throws MWException
         * @return bool Success
index 6f71a51..c8b85ae 100644 (file)
@@ -209,7 +209,7 @@ class SpecialVersion extends SpecialPage {
         * @return string
         */
        public static function softwareInformation() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Put the software in an array of form 'name' => 'version'. All messages should
                // be loaded here, so feel free to use wfMessage in the 'name'. Raw HTML or
index 17d77ba..99f9c7c 100644 (file)
@@ -313,7 +313,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
         * @return IDatabase
         */
        protected function getDB() {
-               return wfGetDB( DB_SLAVE, 'watchlist' );
+               return wfGetDB( DB_REPLICA, 'watchlist' );
        }
 
        /**
@@ -343,7 +343,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $user = $this->getUser();
                $output = $this->getOutput();
 
-               # Show a message about slave lag, if applicable
+               # Show a message about replica DB lag, if applicable
                $lag = wfGetLB()->safeGetLag( $dbr );
                if ( $lag > 0 ) {
                        $output->showLagWarning( $lag );
index baa55f0..1ead290 100644 (file)
@@ -105,7 +105,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
         */
        function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
                $out = $this->getOutput();
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $hidelinks = $this->opts->getValue( 'hidelinks' );
                $hideredirs = $this->opts->getValue( 'hideredirs' );
index 259d1f9..a1e5156 100644 (file)
@@ -97,7 +97,7 @@ class WithoutInterwikiPage extends PageQueryPage {
                        'join_conds' => [ 'langlinks' => [ 'LEFT JOIN', 'll_from = page_id' ] ]
                ];
                if ( $this->prefix ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
                }
 
index 60f642d..5609310 100644 (file)
@@ -184,7 +184,7 @@ class AllMessagesTablePager extends TablePager {
 
        /**
         * Determine which of the MediaWiki and MediaWiki_talk namespace pages exist.
-        * Returns array( 'pages' => ..., 'talks' => ... ), where the subarrays have
+        * Returns [ 'pages' => ..., 'talks' => ... ], where the subarrays have
         * an entry for each existing page, with the key being the message name and
         * value arbitrary.
         *
@@ -196,7 +196,7 @@ class AllMessagesTablePager extends TablePager {
        public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) {
                // FIXME: This function should be moved to Language:: or something.
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select( 'page',
                        [ 'page_namespace', 'page_title' ],
                        [ 'page_namespace' => [ NS_MEDIAWIKI, NS_MEDIAWIKI_TALK ] ],
index f8eba9a..bc0ebc2 100644 (file)
@@ -70,11 +70,11 @@ class ContribsPager extends ReverseChronologicalPager {
                $month = isset( $options['month'] ) ? $options['month'] : false;
                $this->getDateCond( $year, $month );
 
-               // Most of this code will use the 'contributions' group DB, which can map to slaves
+               // Most of this code will use the 'contributions' group DB, which can map to replica DBs
                // with extra user based indexes or partioning by user. The additional metadata
-               // queries should use a regular slave since the lookup pattern is not all by user.
-               $this->mDbSecondary = wfGetDB( DB_SLAVE ); // any random slave
-               $this->mDb = wfGetDB( DB_SLAVE, 'contributions' );
+               // queries should use a regular replica DB since the lookup pattern is not all by user.
+               $this->mDbSecondary = wfGetDB( DB_SLAVE ); // any random replica DB
+               $this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
        }
 
        function getDefaultQuery() {
index f2421f8..1acbba1 100644 (file)
@@ -43,7 +43,7 @@ class DeletedContribsPager extends IndexPager {
                }
                $this->target = $target;
                $this->namespace = $namespace;
-               $this->mDb = wfGetDB( DB_SLAVE, 'contributions' );
+               $this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
        }
 
        function getDefaultQuery() {
index 5e10269..7fc4a95 100644 (file)
@@ -74,7 +74,7 @@ class ImageListPager extends TablePager {
                        $nt = Title::newFromText( $this->mSearch );
 
                        if ( $nt ) {
-                               $dbr = wfGetDB( DB_SLAVE );
+                               $dbr = wfGetDB( DB_REPLICA );
                                $this->mQueryConds[] = 'LOWER(img_name)' .
                                        $dbr->buildLike( $dbr->anyString(),
                                                strtolower( $nt->getDBkey() ), $dbr->anyString() );
@@ -136,7 +136,7 @@ class ImageListPager extends TablePager {
                if ( $this->mSearch !== '' ) {
                        $nt = Title::newFromText( $this->mSearch );
                        if ( $nt ) {
-                               $dbr = wfGetDB( DB_SLAVE );
+                               $dbr = wfGetDB( DB_REPLICA );
                                $conds[] = 'LOWER(' . $prefix . '_name)' .
                                        $dbr->buildLike( $dbr->anyString(),
                                                strtolower( $nt->getDBkey() ), $dbr->anyString() );
@@ -272,7 +272,7 @@ class ImageListPager extends TablePager {
                        }
                        unset( $field );
 
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        if ( $dbr->implicitGroupby() ) {
                                $options = [ 'GROUP BY' => 'img_name' ];
                        } else {
index 0b9587c..56229b3 100644 (file)
@@ -36,7 +36,7 @@ class MergeHistoryPager extends ReverseChronologicalPager {
                $this->title = $source;
                $this->articleID = $source->getArticleID();
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $maxtimestamp = $dbr->selectField(
                        'revision',
                        'MIN(rev_timestamp)',
index d1f9f40..41819fc 100644 (file)
@@ -91,7 +91,7 @@ class NewFilesPager extends ReverseChronologicalPager {
 
                $likeVal = $opts->getValue( 'like' );
                if ( !$this->getConfig()->get( 'MiserMode' ) && $likeVal !== '' ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $likeObj = Title::newFromText( $likeVal );
                        if ( $likeObj instanceof Title ) {
                                $like = $dbr->buildLike(
index adc1dd8..901be38 100644 (file)
@@ -100,7 +100,7 @@ class UsersPager extends AlphabeticPager {
         * @return array
         */
        function getQueryInfo() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $conds = [];
 
                // Don't show hidden names
@@ -228,7 +228,7 @@ class UsersPager extends AlphabeticPager {
                }
 
                // Lookup groups for all the users
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $groupRes = $dbr->select(
                        'user_groups',
                        [ 'ug_user', 'ug_group' ],
index b37c831..1be5c24 100644 (file)
@@ -679,9 +679,23 @@ abstract class UploadBase {
                        $warnings['empty-file'] = true;
                }
 
+               $hash = $this->getTempFileSha1Base36();
                $exists = self::getExistsWarning( $localFile );
                if ( $exists !== false ) {
                        $warnings['exists'] = $exists;
+
+                       // check if file is an exact duplicate of current file version
+                       if ( $hash === $localFile->getSha1() ) {
+                               $warnings['no-change'] = $localFile;
+                       }
+
+                       // check if file is an exact duplicate of older versions of this file
+                       $history = $localFile->getHistory();
+                       foreach ( $history as $oldFile ) {
+                               if ( $hash === $oldFile->getSha1() ) {
+                                       $warnings['duplicate-version'][] = $oldFile;
+                               }
+                       }
                }
 
                if ( $localFile->wasDeleted() && !$localFile->exists() ) {
@@ -689,7 +703,6 @@ abstract class UploadBase {
                }
 
                // Check dupes against existing files
-               $hash = $this->getTempFileSha1Base36();
                $dupes = RepoGroup::singleton()->findBySha1( $hash );
                $title = $this->getTitle();
                // Remove all matches against self
index c171ded..000c6a4 100644 (file)
@@ -499,10 +499,10 @@ class UploadStash {
         * Helper function: do the actual database query to fetch file metadata.
         *
         * @param string $key
-        * @param int $readFromDB Constant (default: DB_SLAVE)
+        * @param int $readFromDB Constant (default: DB_REPLICA)
         * @return bool
         */
-       protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) {
+       protected function fetchFileMetadata( $key, $readFromDB = DB_REPLICA ) {
                // populate $fileMetadata[$key]
                $dbr = null;
                if ( $readFromDB === DB_MASTER ) {
index 49a7163..4ce3cde 100644 (file)
@@ -67,7 +67,7 @@ class BotPassword implements IDBAccessObject {
 
        /**
         * Get a database connection for the bot passwords database
-        * @param int $db Index of the connection to get, e.g. DB_MASTER or DB_SLAVE.
+        * @param int $db Index of the connection to get, e.g. DB_MASTER or DB_REPLICA.
         * @return DatabaseBase
         */
        public static function getDB( $db ) {
index f7c5408..0d8b1a8 100644 (file)
@@ -60,7 +60,7 @@ class LocalIdLookup extends CentralIdLookup {
                }
 
                $audience = $this->checkAudience( $audience );
-               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
                $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
                        ? [ 'LOCK IN SHARE MODE' ]
                        : [];
@@ -93,7 +93,7 @@ class LocalIdLookup extends CentralIdLookup {
                }
 
                $audience = $this->checkAudience( $audience );
-               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+               $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
                $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
                        ? [ 'LOCK IN SHARE MODE' ]
                        : [];
index bc87cd0..889ec92 100644 (file)
@@ -236,7 +236,7 @@ class PasswordReset {
         * @throws MWException On unexpected database errors
         */
        protected function getUsersByEmail( $email ) {
-               $res = wfGetDB( DB_SLAVE )->select(
+               $res = wfGetDB( DB_REPLICA )->select(
                        'user',
                        User::selectFields(),
                        [ 'user_email' => $email ],
index 4ec8d54..7109a4a 100644 (file)
@@ -474,7 +474,7 @@ class User implements IDBAccessObject {
                        $this->getCacheKey( $cache ),
                        $cache::TTL_HOUR,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
-                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
                                wfDebug( "User: cache miss for user {$this->mId}\n" );
 
                                $this->loadFromDatabase( self::READ_NORMAL );
@@ -566,7 +566,7 @@ class User implements IDBAccessObject {
        public static function newFromConfirmationCode( $code, $flags = 0 ) {
                $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
                        ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
+                       : wfGetDB( DB_REPLICA );
 
                $id = $db->selectField(
                        'user',
@@ -897,7 +897,7 @@ class User implements IDBAccessObject {
                        $conds[] = 'ug_user > ' . (int)$after;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $ids = $dbr->selectFieldValues(
                        'user_groups',
                        'ug_user',
@@ -1362,7 +1362,7 @@ class User implements IDBAccessObject {
                if ( is_null( $this->mGroups ) ) {
                        $db = ( $this->queryFlagsUsed & self::READ_LATEST )
                                ? wfGetDB( DB_MASTER )
-                               : wfGetDB( DB_SLAVE );
+                               : wfGetDB( DB_REPLICA );
                        $res = $db->select( 'user_groups',
                                [ 'ug_group' ],
                                [ 'ug_user' => $this->mId ],
@@ -1570,8 +1570,8 @@ class User implements IDBAccessObject {
 
        /**
         * Get blocking information
-        * @param bool $bFromSlave Whether to check the slave database first.
-        *   To improve performance, non-critical checks are done against slaves.
+        * @param bool $bFromSlave Whether to check the replica DB first.
+        *   To improve performance, non-critical checks are done against replica DBs.
         *   Check when actually saving should be done against master.
         */
        private function getBlockedStatus( $bFromSlave = true ) {
@@ -1922,7 +1922,7 @@ class User implements IDBAccessObject {
        /**
         * Check if user is blocked
         *
-        * @param bool $bFromSlave Whether to check the slave database instead of
+        * @param bool $bFromSlave Whether to check the replica DB instead of
         *   the master. Hacked from false due to horrible probs on site.
         * @return bool True if blocked, false otherwise
         */
@@ -1933,7 +1933,7 @@ class User implements IDBAccessObject {
        /**
         * Get the block affecting the user, or null if the user is not blocked
         *
-        * @param bool $bFromSlave Whether to check the slave database instead of the master
+        * @param bool $bFromSlave Whether to check the replica DB instead of the master
         * @return Block|null
         */
        public function getBlock( $bFromSlave = true ) {
@@ -1945,7 +1945,7 @@ class User implements IDBAccessObject {
         * Check if user is blocked from editing a particular article
         *
         * @param Title $title Title to check
-        * @param bool $bFromSlave Whether to check the slave database instead of the master
+        * @param bool $bFromSlave Whether to check the replica DB instead of the master
         * @return bool
         */
        public function isBlockedFrom( $title, $bFromSlave = false ) {
@@ -2190,7 +2190,7 @@ class User implements IDBAccessObject {
                        return [];
                }
                $utp = $this->getTalkPage();
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                // Get the "last viewed rev" timestamp from the oldest message notification
                $timestamp = $dbr->selectField( 'user_newtalk',
                        'MIN(user_last_timestamp)',
@@ -2233,7 +2233,7 @@ class User implements IDBAccessObject {
         * @return bool True if the user has new messages
         */
        protected function checkNewtalk( $field, $id ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
 
@@ -3243,7 +3243,7 @@ class User implements IDBAccessObject {
                if ( is_null( $this->mFormerGroups ) ) {
                        $db = ( $this->queryFlagsUsed & self::READ_LATEST )
                                ? wfGetDB( DB_MASTER )
-                               : wfGetDB( DB_SLAVE );
+                               : wfGetDB( DB_REPLICA );
                        $res = $db->select( 'user_former_groups',
                                [ 'ufg_group' ],
                                [ 'ufg_user' => $this->mId ],
@@ -3268,7 +3268,7 @@ class User implements IDBAccessObject {
 
                if ( $this->mEditCount === null ) {
                        /* Populate the count, if it has not been populated yet */
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        // check if the user_editcount field has been initialized
                        $count = $dbr->selectField(
                                'user', 'user_editcount',
@@ -3595,7 +3595,7 @@ class User implements IDBAccessObject {
 
                // Only update the timestamp if the page is being watched.
                // The query to find out if it is watched is cached both in memcached and per-invocation,
-               // and when it does have to be executed, it can be on a slave
+               // and when it does have to be executed, it can be on a replica DB
                // If this is the user's newtalk page, we always update the timestamp
                $force = '';
                if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
@@ -3818,7 +3818,7 @@ class User implements IDBAccessObject {
 
                // Get a new user_touched that is higher than the old one.
                // This will be used for a CAS check as a last-resort safety
-               // check against race conditions and slave lag.
+               // check against race conditions and replica DB lag.
                $newTouched = $this->newTouchedTimestamp();
 
                $dbw = wfGetDB( DB_MASTER );
@@ -3841,7 +3841,7 @@ class User implements IDBAccessObject {
                        // Maybe the problem was a missed cache update; clear it to be safe
                        $this->clearSharedCache( 'refresh' );
                        // User was changed in the meantime or loaded with stale data
-                       $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'slave';
+                       $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
                        throw new MWException(
                                "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
                                " the version of the user to be saved is older than the current version."
@@ -3870,7 +3870,7 @@ class User implements IDBAccessObject {
 
                $db = ( ( $flags & self::READ_LATEST ) == self::READ_LATEST )
                        ? wfGetDB( DB_MASTER )
-                       : wfGetDB( DB_SLAVE );
+                       : wfGetDB( DB_REPLICA );
 
                $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
                        ? [ 'LOCK IN SHARE MODE' ]
@@ -4510,7 +4510,7 @@ class User implements IDBAccessObject {
                if ( $this->getId() == 0 ) {
                        return false; // anons
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $time = $dbr->selectField( 'revision', 'rev_timestamp',
                        [ 'rev_user' => $this->getId() ],
                        __METHOD__,
@@ -4915,14 +4915,14 @@ class User implements IDBAccessObject {
                // Lazy initialization check...
                if ( $dbw->affectedRows() == 0 ) {
                        // Now here's a goddamn hack...
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        if ( $dbr !== $dbw ) {
-                               // If we actually have a slave server, the count is
+                               // If we actually have a replica DB server, the count is
                                // at least one behind because the current transaction
                                // has not been committed and replicated.
                                $this->mEditCount = $this->initEditCount( 1 );
                        } else {
-                               // But if DB_SLAVE is selecting the master, then the
+                               // But if DB_REPLICA is selecting the master, then the
                                // count we just read includes the revision that was
                                // just added in the working transaction.
                                $this->mEditCount = $this->initEditCount();
@@ -4930,7 +4930,7 @@ class User implements IDBAccessObject {
                } else {
                        if ( $this->mEditCount === null ) {
                                $this->getEditCount();
-                               $dbr = wfGetDB( DB_SLAVE );
+                               $dbr = wfGetDB( DB_REPLICA );
                                $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
                        } else {
                                $this->mEditCount++;
@@ -4947,9 +4947,9 @@ class User implements IDBAccessObject {
         * @return int Number of edits
         */
        protected function initEditCount( $add = 0 ) {
-               // Pull from a slave to be less cruel to servers
+               // Pull from a replica DB to be less cruel to servers
                // Accuracy isn't the point anyway here
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $count = (int)$dbr->selectField(
                        'revision',
                        'COUNT(rev_user)',
@@ -5107,7 +5107,7 @@ class User implements IDBAccessObject {
                                // Load from database
                                $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
                                        ? wfGetDB( DB_MASTER )
-                                       : wfGetDB( DB_SLAVE );
+                                       : wfGetDB( DB_REPLICA );
 
                                $res = $dbr->select(
                                        'user_properties',
@@ -5173,7 +5173,7 @@ class User implements IDBAccessObject {
                        [ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
 
                // Find prior rows that need to be removed or updated. These rows will
-               // all be deleted (the later so that INSERT IGNORE applies the new values).
+               // all be deleted (the latter so that INSERT IGNORE applies the new values).
                $keysDelete = [];
                foreach ( $res as $row ) {
                        if ( !isset( $saveOptions[$row->up_property] )
@@ -5317,7 +5317,7 @@ class User implements IDBAccessObject {
         * Get a new instance of this user that was loaded from the master via a locking read
         *
         * Use this instead of the main context User when updating that user. This avoids races
-        * where that user was loaded from a slave or even the master but without proper locks.
+        * where that user was loaded from a replica DB or even the master but without proper locks.
         *
         * @return User|null Returns null if the user was not found in the DB
         * @since 1.27
index a4d4356..dddc850 100644 (file)
@@ -46,7 +46,7 @@ abstract class UserArray implements Iterator {
                        // Database::select() doesn't like empty arrays
                        return new ArrayIterator( [] );
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select(
                        'user',
                        User::selectFields(),
@@ -67,7 +67,7 @@ abstract class UserArray implements Iterator {
                        // Database::select() doesn't like empty arrays
                        return new ArrayIterator( [] );
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $res = $dbr->select(
                        'user',
                        User::selectFields(),
index 4a27a13..b7d5058 100644 (file)
@@ -39,7 +39,7 @@ class UserNamePrefixSearch {
        public static function search( $audience, $search, $limit, $offset = 0 ) {
                $user = User::newFromName( $search );
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $prefix = $user ? $user->getName() : '';
                $tables = [ 'user' ];
                $cond = [ 'user_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ) ];
index 549ad41..1e7eda8 100644 (file)
@@ -4,7 +4,7 @@
  * method of batch updating rows in a database. To use create a class
  * implementing the RowUpdateGenerator interface and configure the
  * BatchRowIterator and BatchRowWriter for access to the correct table.
- * The components will handle reading, writing, and waiting for slaves
+ * The components will handle reading, writing, and waiting for replica DBs
  * while the generator implementation handles generating update arrays
  * for singular rows.
  *
index 6a4792c..342dffd 100644 (file)
@@ -26,10 +26,10 @@ interface RowUpdateGenerator {
         * updated value within the database row.
         *
         * Sample Response:
-        *   return array(
+        *   return [
         *       'some_col' => 'new value',
         *       'other_col' => 99,
-        *   );
+        *   ];
         *
         * @param stdClass $row A row from the database
         * @return array Map of column names to updated value within the
index 37a0584..a15d910 100644 (file)
@@ -3175,6 +3175,7 @@ public static $zh2Hant = [
 '上系上' => '上繫上',
 '上课钟' => '上課鐘',
 '上面糊' => '上面糊',
+'下文里' => '下文裡',
 '下于' => '下於',
 '下梁' => '下樑',
 '下注解' => '下注解',
@@ -3231,7 +3232,6 @@ public static $zh2Hant = [
 '丑表功' => '丑表功',
 '丑角' => '丑角',
 '且于' => '且於',
-'世田谷' => '世田谷',
 '世界杯' => '世界盃',
 '世纪里' => '世紀裡',
 '世纪钟' => '世紀鐘',
@@ -3362,6 +3362,7 @@ public static $zh2Hant = [
 '干性' => '乾性',
 '干打雷' => '乾打雷',
 '干折' => '乾折',
+'干拌面' => '乾拌麵',
 '干撂台' => '乾撂台',
 '干撇下' => '乾撇下',
 '干擦' => '乾擦',
@@ -3477,6 +3478,7 @@ public static $zh2Hant = [
 '乱发生' => '亂發生',
 '乱发脾气' => '亂發脾氣',
 '乱发' => '亂髮',
+'乱斗' => '亂鬥',
 '乱哄哄' => '亂鬨鬨',
 '了然后' => '了然後',
 '事有斗巧' => '事有鬥巧',
@@ -4087,8 +4089,6 @@ public static $zh2Hant = [
 '刑余' => '刑餘',
 '划一桨' => '划一槳',
 '划一槳' => '划一槳',
-'划上' => '划上',
-'划下' => '划下',
 '划不來' => '划不來',
 '划不来' => '划不來',
 '划了一会' => '划了一會',
@@ -4204,6 +4204,8 @@ public static $zh2Hant = [
 '北回铁路' => '北迴鐵路',
 '匪干' => '匪幹',
 '匿于' => '匿於',
+'区里有' => '區裡有',
+'区里的' => '區裡的',
 '十个' => '十個',
 '十出家' => '十出家',
 '十出击' => '十出擊',
@@ -4263,6 +4265,7 @@ public static $zh2Hant = [
 '卷须' => '卷鬚',
 '厂部' => '厂部',
 '原子钟' => '原子鐘',
+'原文里' => '原文裡',
 '原钟' => '原鐘',
 '历物之意' => '厤物之意',
 '去山里' => '去山裡',
@@ -4291,6 +4294,7 @@ public static $zh2Hant = [
 '口里' => '口裡',
 '口钟' => '口鐘',
 '古人有云' => '古人有云',
+'古文里' => '古文裡',
 '古书云' => '古書云',
 '古書云' => '古書云',
 '古柯咸' => '古柯鹹',
@@ -4842,6 +4846,7 @@ public static $zh2Hant = [
 '寡欲' => '寡慾',
 '实干' => '實幹',
 '实累累' => '實纍纍',
+'实验里' => '實驗裡',
 '写字台' => '寫字檯',
 '宽于' => '寬於',
 '宽余' => '寬餘',
@@ -5068,6 +5073,7 @@ public static $zh2Hant = [
 '干革命' => '幹革命',
 '干头' => '幹頭',
 '干么' => '幹麼',
+'幽并' => '幽并',
 '几个' => '幾個',
 '几周后' => '幾周後',
 '几天后' => '幾天後',
@@ -5392,6 +5398,7 @@ public static $zh2Hant = [
 '怒气冲天' => '怒氣衝天',
 '怒火冲天' => '怒火衝天',
 '怒发冲冠' => '怒髮衝冠',
+'怜奈' => '怜奈',
 '思如泉涌' => '思如泉湧',
 '怠于' => '怠於',
 '急于' => '急於',
@@ -5597,6 +5604,7 @@ public static $zh2Hant = [
 '拉面色' => '拉面色',
 '拉面部' => '拉面部',
 '拉面' => '拉麵',
+'拌面' => '拌麵',
 '拒人于' => '拒人於',
 '拒于' => '拒於',
 '拓朴' => '拓樸',
@@ -5695,6 +5703,7 @@ public static $zh2Hant = [
 '捶炼' => '捶鍊',
 '扫荡' => '掃蕩',
 '授勋' => '授勳',
+'授时历' => '授時曆',
 '掌柜' => '掌柜',
 '排干' => '排乾',
 '排干部' => '排幹部',
@@ -5910,6 +5919,7 @@ public static $zh2Hant = [
 '斫雕为朴' => '斫雕為樸',
 '新井里美' => '新井里美',
 '新干县' => '新幹縣',
+'新庄子' => '新庄子',
 '新历' => '新曆',
 '新历史' => '新歷史',
 '新扎' => '新紮',
@@ -6074,6 +6084,7 @@ public static $zh2Hant = [
 '村落发' => '村落發',
 '村里有' => '村裡有',
 '村里的' => '村裡的',
+'杜琪峰' => '杜琪峯',
 '杜老志道' => '杜老誌道',
 '杞宋无征' => '杞宋無徵',
 '束发' => '束髮',
@@ -6141,6 +6152,7 @@ public static $zh2Hant = [
 '梁上君子' => '梁上君子',
 '梁启超' => '梁啓超',
 '条干' => '條幹',
+'条文里' => '條文裡',
 '梨干' => '梨乾',
 '梯冲' => '梯衝',
 '械系' => '械繫',
@@ -6232,6 +6244,7 @@ public static $zh2Hant = [
 '欧游' => '歐遊',
 '止于' => '止於',
 '正官庄' => '正官庄',
+'正文里' => '正文裡',
 '正杰' => '正杰',
 '武丑' => '武丑',
 '武后' => '武后',
@@ -6274,8 +6287,6 @@ public static $zh2Hant = [
 '水并流' => '水併流',
 '水来汤里去' => '水來湯裡去',
 '水准' => '水準',
-'水无怜奈' => '水無怜奈',
-'水無怜奈' => '水無怜奈',
 '水表示' => '水表示',
 '水表面' => '水表面',
 '水里' => '水裡',
@@ -6333,7 +6344,6 @@ public static $zh2Hant = [
 '泰山梁木' => '泰山梁木',
 '泱郁' => '泱鬱',
 '泳气钟' => '泳氣鐘',
-'洄游' => '洄遊',
 '洋河大曲' => '洋河大麯',
 '洒家' => '洒家',
 '洒淅' => '洒淅',
@@ -6380,6 +6390,8 @@ public static $zh2Hant = [
 '涂敏恒' => '涂敏恆',
 '涂泽民' => '涂澤民',
 '涂澤民' => '涂澤民',
+'涂尔干' => '涂爾幹',
+'涂爾幹' => '涂爾幹',
 '涂紹煃' => '涂紹煃',
 '涂绍煃' => '涂紹煃',
 '涂羽卿' => '涂羽卿',
@@ -6712,21 +6724,18 @@ public static $zh2Hant = [
 '甜面酱' => '甜麵醬',
 '生力面' => '生力麵',
 '生于' => '生於',
-'生殖洄游' => '生殖洄游',
 '生物钟' => '生物鐘',
 '生发生' => '生發生',
 '生华发' => '生華髮',
 '生姜' => '生薑',
 '生锈' => '生鏽',
 '生发' => '生髮',
-'产卵洄游' => '產卵洄游',
 '苏醒' => '甦醒',
 '用于' => '用於',
 '用法里' => '用法裡',
 '甩发' => '甩髮',
 '田子里' => '田子里',
 '田庄英雄' => '田庄英雄',
-'田谷' => '田穀',
 '田里' => '田裡',
 '由余' => '由余',
 '由于' => '由於',
@@ -6743,6 +6752,7 @@ public static $zh2Hant = [
 '毕于' => '畢於',
 '毕业于' => '畢業於',
 '毕生发展' => '畢生發展',
+'画里' => '畫裡',
 '当准' => '當準',
 '当当丁丁' => '當當丁丁',
 '当当网' => '當當網',
@@ -7110,7 +7120,6 @@ public static $zh2Hant = [
 '吁求' => '籲求',
 '吁请' => '籲請',
 '米沈' => '米瀋',
-'米谷' => '米穀',
 '米团' => '米糰',
 '米余' => '米餘',
 '米面' => '米麵',
@@ -7190,6 +7199,7 @@ public static $zh2Hant = [
 '绑扎' => '綁紮',
 '绥棱' => '綏稜',
 '捆扎' => '綑紮',
+'经文里' => '經文裡',
 '經有云' => '經有云',
 '经有云' => '經有云',
 '综合征' => '綜合徵',
@@ -7248,7 +7258,6 @@ public static $zh2Hant = [
 '系上头' => '繫上頭',
 '系上黑' => '繫上黑',
 '系上,' => '繫上,',
-'系世' => '繫世',
 '系到' => '繫到',
 '系囚' => '繫囚',
 '系心' => '繫心',
@@ -7314,6 +7323,7 @@ public static $zh2Hant = [
 '老板' => '老闆',
 '老面皮' => '老面皮',
 '考征' => '考徵',
+'考试制度' => '考試制度',
 '耍斗' => '耍鬥',
 '耕获' => '耕穫',
 '耳余' => '耳餘',
@@ -7472,6 +7482,7 @@ public static $zh2Hant = [
 '苦里' => '苦裡',
 '苦斗' => '苦鬥',
 '苧麻' => '苧麻',
+'英文里' => '英文裡',
 '茂都淀' => '茂都澱',
 '范文同' => '范文同',
 '范文正公' => '范文正公',
@@ -7842,7 +7853,6 @@ public static $zh2Hant = [
 '西历' => '西曆',
 '西历史' => '西歷史',
 '西湖里' => '西湖里',
-'西米谷' => '西米谷',
 '西西里' => '西西里',
 '西谷米' => '西谷米',
 '西游' => '西遊',
@@ -7912,6 +7922,7 @@ public static $zh2Hant = [
 '注生娘娘' => '註生娘娘',
 '注疏' => '註疏',
 '注脚' => '註腳',
+'注里' => '註裡',
 '注解' => '註解',
 '注记' => '註記',
 '注译' => '註譯',
@@ -8004,6 +8015,7 @@ public static $zh2Hant = [
 '谈征' => '談徵',
 '请君入瓮' => '請君入甕',
 '请托' => '請託',
+'论文里' => '論文裡',
 '咨询' => '諮詢',
 '诸余' => '諸餘',
 '谋干' => '謀幹',
@@ -8027,6 +8039,7 @@ public static $zh2Hant = [
 '警报钟' => '警報鐘',
 '警示钟' => '警示鐘',
 '警钟' => '警鐘',
+'译文里' => '譯文裡',
 '译制' => '譯製',
 '译注' => '譯註',
 '护发' => '護髮',
@@ -8827,6 +8840,7 @@ public static $zh2Hant = [
 '额我略历' => '額我略曆',
 '额我略历史' => '額我略歷史',
 '颜范' => '顏範',
+'颛顼历' => '顓頊曆',
 '颠干倒坤' => '顛乾倒坤',
 '顛顛仆仆' => '顛顛仆仆',
 '颠颠仆仆' => '顛顛仆仆',
@@ -14175,7 +14189,6 @@ public static $zh2TW = [
 '打印度' => '打印度',
 '抽烟' => '抽菸',
 '抽煙' => '抽菸',
-'拉普兰' => '拉布蘭',
 '拒烟' => '拒菸',
 '拒煙' => '拒菸',
 '卷烟' => '捲菸',
@@ -14661,6 +14674,7 @@ public static $zh2TW = [
 '迈凯轮' => '麥拿輪',
 '邁凱輪' => '麥拿輪',
 '马萨诸塞' => '麻薩諸塞',
+'粘膜' => '黏膜',
 '戴安娜' => '黛安娜',
 '狄安娜' => '黛安娜',
 '点烟' => '點菸',
@@ -14689,6 +14703,7 @@ public static $zh2HK = [
 '旧金山' => '三藩市',
 '舊金山' => '三藩市',
 '上台面' => '上枱面',
+'下文里' => '下文裏',
 '下著' => '下着',
 '下著作' => '下著作',
 '下著名' => '下著名',
@@ -15174,6 +15189,8 @@ public static $zh2HK = [
 '動著錄' => '動著錄',
 '包著' => '包着',
 '北朝鲜' => '北韓',
+'区里有' => '區裏有',
+'区里的' => '區裏的',
 '南朝鲜' => '南韓',
 '波札那' => '博茨瓦納',
 '占卜' => '占卜',
@@ -15202,6 +15219,7 @@ public static $zh2HK = [
 '厄利垂亚' => '厄立特里亞',
 '厄利垂亞' => '厄立特里亞',
 '源代码' => '原始碼',
+'原文里' => '原文裏',
 '去山里' => '去山裏',
 '参数里' => '參數裏',
 '受著' => '受着',
@@ -15214,6 +15232,7 @@ public static $zh2HK = [
 '受著錄' => '受著錄',
 '丛林里' => '叢林裏',
 '口里' => '口裏',
+'古文里' => '古文裏',
 '只占' => '只佔',
 '叫著' => '叫着',
 '叫著作' => '叫著作',
@@ -16148,6 +16167,7 @@ public static $zh2HK = [
 '撞球' => '桌球',
 '梅鐸' => '梅鐸',
 '默多克' => '梅鐸',
+'条文里' => '條文裏',
 '梳著' => '梳着',
 '梳著作' => '梳著作',
 '梳著名' => '梳著名',
@@ -16178,6 +16198,7 @@ public static $zh2HK = [
 '機器人' => '機械人',
 '柜台' => '櫃枱',
 '柜里' => '櫃裏',
+'正文里' => '正文裏',
 '历史里' => '歷史裏',
 '死里求生' => '死裏求生',
 '死里逃生' => '死裏逃生',
@@ -16463,6 +16484,7 @@ public static $zh2HK = [
 '畫著名' => '畫著名',
 '畫著稱' => '畫著稱',
 '畫著者' => '畫著者',
+'画里' => '畫裏',
 '當著' => '當着',
 '當著作' => '當著作',
 '過著作' => '當著作',
@@ -16743,6 +16765,7 @@ public static $zh2HK = [
 '綁著者' => '綁著者',
 '綁著述' => '綁著述',
 '綁著錄' => '綁著錄',
+'经文里' => '經文裏',
 '网站里' => '網站裏',
 '網路' => '網絡',
 '网里' => '網裏',
@@ -16907,6 +16930,7 @@ public static $zh2HK = [
 '苦著錄' => '苦著錄',
 '苦里' => '苦裏',
 '英占' => '英佔',
+'英文里' => '英文裏',
 '共和联邦' => '英聯邦',
 '大英國協' => '英聯邦',
 '草丛里' => '草叢裏',
@@ -17099,8 +17123,10 @@ public static $zh2HK = [
 '說著者' => '說著者',
 '說著述' => '說著述',
 '數據機' => '調制解調器',
+'论文里' => '論文裏',
 '诺曼底' => '諾曼第',
 '警戒著' => '警戒着',
+'譯文里' => '譯文裏',
 '變著' => '變着',
 '變著作' => '變著作',
 '變著名' => '變著名',
index 58a91bf..6bc5553 100644 (file)
@@ -23,6 +23,7 @@
        "tog-hideminor": "Hȳdan lytela adihtunga in nīwra andwendinga getæle",
        "tog-hidepatrolled": "Hȳdan weardoda adihtunga in nīwra andwendinga getæle",
        "tog-newpageshidepatrolled": "Hȳdan weardode trametas in nīwra andwendinga getæle",
+       "tog-hidecategorization": "Behȳd trameta floccas",
        "tog-extendwatchlist": "Sprǣdan behealdungtæl tō īwenne ealla andwendinga, nā synderlīce þā nīwostan",
        "tog-usenewrc": "Settan andwendunga on hēapas on trametum on nīwra andwendunga getæle and behealdungtæle",
        "tog-numberheadings": "Settan rīm on forecwidas selflīce",
@@ -33,6 +34,7 @@
        "tog-watchdefault": "Ēacnian mīn behealdungtæl mid trametum and ymelum þā ic adihte.",
        "tog-watchmoves": "Ēacnian mīn behealdungtæl mid trametum and ymelum þā ic wege.",
        "tog-watchdeletion": "Ēacnian mīn behealdungæl mid trametum and ymelum þā ic forlēose.",
+       "tog-watchuploads": "Eacne nīwa ymelan to mīnum weardgetæle",
        "tog-minordefault": "Mearcian ealla adihtunga lytela tō gewunan",
        "tog-previewontop": "Īwan forebysene ofer adihtunge mearce",
        "tog-previewonfirst": "Īwan forebysene on forman adihtunge",
        "yourpasswordagain": "Wrītan þafungword eft:",
        "createacct-yourpasswordagain": "Asēð þafungword",
        "createacct-yourpasswordagain-ph": "Wrīt þafungword eft",
-       "remembermypassword": "Gemynan mīne inmeldunge on þissum webbsēcende (oþ $1 {{PLURAL:$1|dæg|daga}} lengest)",
        "userlogin-remembermypassword": "Ætfeolan mīnre inmeldunge",
        "yourdomainname": "Þīn geweald:",
        "password-change-forbidden": "Þū ne canst awendan þafungword on þissum wiki.",
index fdbce6e..1b6acc9 100644 (file)
@@ -66,7 +66,8 @@
                        "علاء",
                        "Hhaboh162002",
                        "بدارين",
-                       "باسم"
+                       "باسم",
+                       "Moud hosny"
                ]
        },
        "tog-underline": "سطر تحت الوصلات:",
        "yourpasswordagain": "أعد كتابة كلمة السر:",
        "createacct-yourpasswordagain": "أكد كلمة السر",
        "createacct-yourpasswordagain-ph": "أدخل كلمة المرور مرة أخرى",
-       "remembermypassword": "تذكر دخولي بهذا المتصفح (لمدة أقصاها {{PLURAL:$1||يوم واحد|يومان|$1 أيام|$1 يوما|$1 يوم}})",
        "userlogin-remembermypassword": "أبقني مسجلا للدخول",
        "userlogin-signwithsecure": "الولوج باتصّال مؤمّن",
+       "cannotlogin-title": "لا يمكن تسجيل الدخول",
+       "cannotlogin-text": "تسجيل الدخول غير ممكن.",
        "cannotloginnow-title": "لا يمكن تسجيل الدخول الآن",
        "cannotloginnow-text": "لا يمكن تسجيل الدخول عند استخدام $1.",
+       "cannotcreateaccount-title": "لا يمكن إنشاء الحسابات",
+       "cannotcreateaccount-text": "إنشاء الحسابات المباشر غير مفعل على هذه الويكي.",
        "yourdomainname": "نطاقك:",
        "password-change-forbidden": "أنت لا يمكنك تغيير كلمات السر على هذا الويكي.",
        "externaldberror": "هناك إما خطأ في دخول قاعدة البيانات الخارجية أو أنه غير مسموح لك بتحديث حسابك الخارجي.",
        "file-thumbnail-no": "يبدأ الملف ب <strong>$1</strong>.\nيبدو أن الملف مصغرا لحجم أعلى ''(تصغير)''.\nإذا كانت لديك الصورة في درجة دقة كاملة قم برفعها، أو قم بتغيير اسم الملف من فضلك.",
        "fileexists-forbidden": "هناك ملف موجود بهذا الاسم بالفعل، ولا يمكن إعادة الكتابة عليه.\nلو أنك مازلت تريد رفع ملفك، من فضلك عد واستخدم اسماً جديداً. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "يوجد ملف بنفس الاسم بالفعل في مستودع الملفات المشترك.\nلو كنت مازلت تريد رفع ملفك، من فضلك ارجع واستخدم اسماً جديداً.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "الملف المرفوع هو نسخة مطابقة تمامًا للنسخة الحالية من <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "الملف المرفوع هو نسخة مطابقة من {{PLURAL:$2|نسخة أقدم|نسخ أقدم}} من <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "هذا الملف مكرر  {{PLURAL:$1|للملف|للملفات}} التالية:",
        "file-deleted-duplicate": "ملف مطابق لهذه الملف ([[:$1]]) تم حذفه من قبل. ينبغي أن تتحقق من تاريخ الحذف لهذا الملف قبل المتابعة بإعادة رفعه.",
        "file-deleted-duplicate-notitle": "سابقا تم حذف ملف مطابق لهذا الملف، وقد تم منع العنوان.\nينبغي أن تسأل شخص ما لديه القدرة على عرض بيانات الملف الممنوع لاستعراض الوضع قبل الشروع في إعادة تحميله.",
        "filerevert-submit": "استرجع",
        "filerevert-success": "'''[[Media:$1|$1]]''' تم استرجاعها [$4 للنسخة بتاريخ $3، $2].",
        "filerevert-badversion": "لا توجد نسخة محلية سابقة لهذا الملف بالتاريخ المعطى.",
+       "filerevert-identical": "الإصدار الحالي من الملف بالفعل مطابق للإصدار المحدد.",
        "filedelete": "احذف $1",
        "filedelete-legend": "احذف الملف",
        "filedelete-intro": "أنت على وشك حذف الملف '''[[Media:$1|$1]]''' مع كل تاريخه.",
        "pageinfo-article-id": "معرف الصفحة (ID)",
        "pageinfo-language": "لغة محتوى الصفحة",
        "pageinfo-content-model": "نموذج محتوى الصفحة",
+       "pageinfo-content-model-change": "تغيير",
        "pageinfo-robot-policy": "فهرسة الروبوتات",
        "pageinfo-robot-index": "مسموح بها",
        "pageinfo-robot-noindex": "غير مسموح بها",
index 417ca02..bd2a0a3 100644 (file)
                        "Fitoschido",
                        "Macofe",
                        "Matma Rex",
-                       "Tokvo"
+                       "Tokvo",
+                       "Crucifunked"
                ]
        },
        "tog-underline": "Sorrayar enllaces:",
        "tog-hideminor": "Anubrir les ediciones menores nos cambeos recientes",
        "tog-hidepatrolled": "Anubrir les ediciones vixilaes nos cambeos recientes",
        "tog-newpageshidepatrolled": "Anubrir les páxines vixilaes na llista de páxines nueves",
-       "tog-hidecategorization": "Tapecer la categorización de páxines",
+       "tog-hidecategorization": "Anubrir la categorización de páxines",
        "tog-extendwatchlist": "Espander la llista de siguimientu p'amosar tolos cambeos, non solo los más recientes",
        "tog-usenewrc": "Agrupar los cambeos por páxina nos cambeos recientes y na llista de siguimientu",
        "tog-numberheadings": "Autonumberar los encabezaos",
        "period-am": "AM",
        "period-pm": "PM",
        "pagecategories": "{{PLURAL:$1|Categoría|Categoríes}}",
-       "category_header": "Páxines na categoría «$1»",
+       "category_header": "Páxines na categoría \"$1\"",
        "subcategories": "Subcategoríes",
-       "category-media-header": "Ficheros multimedia na categoría «$1»",
-       "category-empty": "''Anguaño esta categoría nun tien nengún artículu nin ficheru multimedia.''",
+       "category-media-header": "Ficheros multimedia na categoría \"$1\"",
+       "category-empty": "<em>Anguaño esta categoría nun tien nengún artículu nin ficheru multimedia.</em>",
        "hidden-categories": "{{PLURAL:$1|Categoría anubrida|Categoríes anubríes}}",
        "hidden-category-category": "Categoríes anubríes",
        "category-subcat-count": "{{PLURAL:$2|Esta categoría tien namái la subcategoría siguiente.|Esta categoría tien {{PLURAL:$1|la siguiente subcategoría|les siguientes $1 subcategoríes}}, d'un total de $2.}}",
        "yourpasswordagain": "Escribi otra vuelta la contraseña:",
        "createacct-yourpasswordagain": "Confirmar la contraseña",
        "createacct-yourpasswordagain-ph": "Escriba nuevamente la contraseña",
-       "remembermypassword": "Recordar la mio identificación nesti restolador (un máximu {{PLURAL:$1|d'un día|de $1 díes}})",
        "userlogin-remembermypassword": "Caltener abierta la sesión",
        "userlogin-signwithsecure": "Usar una conexón segura",
        "cannotloginnow-title": "Nun puede aniciase sesión agora",
        "file-thumbnail-no": "El ficheru entama con <strong>$1</strong>.\nPaez ser una imaxe de tamañu menguáu ''(miniatura)''.\nSi tienes esta imaxe a resolución completa xúbila; si non, por favor camuda'l nome del ficheru.",
        "fileexists-forbidden": "Yá esiste un ficheru con esti nome, y nun se pue renomar.\nSi tovía asina quies xubir el ficheru, por favor vuelvi atrás y usa otru nome.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Yá esiste un ficheru con esti nome nel direutoriu de ficheros compartíos.\nSi tovía asina quies xubir el ficheru, por favor vuelvi atrás y usa otru nome.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "La carga ye un duplicáu exautu de la versión actual de <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "La carga ye un duplicáu exautu {{PLURAL:$2|d'una versión más vieya|de versiones más vieyes}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Esti ficheru ye un duplicáu {{PLURAL:$1|del siguiente ficheru|de los siguientes ficheros}}:",
        "file-deleted-duplicate": "Yá se desanició enantes un ficheru idénticu a esti ([[:$1]]).\nDeberíes revisar el historial de desaniciu del ficheru enantes de xubilu otra vuelta.",
        "file-deleted-duplicate-notitle": "Un ficheru idénticu a esti desanicióse anteriormente, y suprimióse'l títulu. Tendría de pidir a dalguién que pueda ver los datos del ficheru desaniciáu que revise la situación enantes de volver a xubilu.",
        "filerevert-submit": "Revertir",
        "filerevert-success": "'''[[Media:$1|$1]]''' foi revertida a la [$4 versión del $3 a les $2].",
        "filerevert-badversion": "Nun hai nenguna versión llocal previa d'esti archivu cola fecha conseñada.",
+       "filerevert-identical": "La versión actual del ficheru ye igual que la seleicionada.",
        "filedelete": "Desaniciar $1",
        "filedelete-legend": "Esborrar archivu",
        "filedelete-intro": "Tas a piques d'esborrar el ficheru '''[[Media:$1|$1]]''' xunto con tol so historial.",
        "rollbacklinkcount-morethan": "revertir más de $1 {{PLURAL:$1|edición|ediciones}}",
        "rollbackfailed": "Falló la reversión",
        "rollback-missingparam": "Faltan parámetros riquíos na solicitú.",
+       "rollback-missingrevision": "Nun pueden cargase los datos de la revisión.",
        "cantrollback": "Nun se pue revertir la edición; el postrer collaborador ye l'únicu autor d'esta páxina.",
        "alreadyrolled": "Nun se pue revertir la postrer edición de [[:$1]] fecha por [[User:$2|$2]] ([[User talk:$2|alderique]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\ndaquién más yá editó o revirtió la páxina.\n\nLa postrer edición foi fecha por [[User:$3|$3]] ([[User talk:$3|alderique]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "El resume de la edición yera: <em>$1</em>.",
        "linkaccounts-submit": "Enllazar cuentes",
        "unlinkaccounts": "Desenllazar cuentes",
        "unlinkaccounts-success": "Desenllazóse la cuenta.",
-       "authenticationdatachange-ignored": "Nun se xestionó'l cambéu de los datos d'autentificacion. ¿Seique, nun se configuró un fornidor?"
+       "authenticationdatachange-ignored": "Nun se xestionó'l cambéu de los datos d'autentificacion. ¿Seique, nun se configuró un fornidor?",
+       "userjsispublic": "Atención: les subpáxines JavaScript nun tendríen de contener datos acutaos porque son visibles pa otros usuarios.",
+       "usercssispublic": "Atención: les subpáxines CSS nun tendríen de contener datos acutaos porque son visibles pa otros usuarios."
 }
index 14bf745..69ff42b 100644 (file)
        "yourpasswordagain": "Паўтарыце пароль:",
        "createacct-yourpasswordagain": "Пацьвердзіце пароль",
        "createacct-yourpasswordagain-ph": "Увядзіце пароль зноў",
-       "remembermypassword": "Запомніць мяне на гэтым кампутары (ня больш за $1 {{PLURAL:$1|дзень|дні|дзён}})",
        "userlogin-remembermypassword": "Запомніць мяне",
        "userlogin-signwithsecure": "Скарыстацца бясьпечным злучэньнем",
        "cannotloginnow-title": "Цяпер немагчыма ўвайсьці",
        "file-thumbnail-no": "Назва файла пачынаецца з <strong>$1</strong>.\nВерагодна гэта паменшаная копія выявы ''(мініятура)''.\nКалі Вы маеце гэтую выяву ў поўным памеры, загрузіце яе, альбо зьмяніце назву файла.",
        "fileexists-forbidden": "Файл з такой назвай ужо існуе і ня можа быць перапісаны.\nКалі ласка, вярніцеся назад і загрузіце гэты файл з новай назвай. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Файл з такой назвай ужо існуе ў агульным сховішчы файлаў.\nКалі Вы жадаеце загрузіць Ваш файл, вярніцеся назад і загрузіце гэты файл з новай назвай. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Гэтая загрузка зьяўляецца дакладнай копіяй цяперашняй вэрсіі <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Гэтая загрузка зьяўляецца дакладнай копіяй {{PLURAL:$2|1=старой вэрсіі|старых вэрсіяў}} файлу <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Гэты файл дублюе {{PLURAL:$1|1=наступны файл|наступныя файлы}}:",
        "file-deleted-duplicate": "Падобны файл ([[:$1]]) ужо выдаляўся. Калі ласка, паглядзіце гісторыю выдаленьняў гэтага файла перад яго паўторнай загрузкай.",
        "file-deleted-duplicate-notitle": "Файл, ідэнтычны гэтаму файлу, раней ужо быў выдалены, а назва файла была забароненая.\nВам трэба зьвярнуцца да некага з правамі прагляду зьвестак забароненых файлаў, каб прааналізаваць сытуацыю перад тым, як загружаць файл ізноў.",
        "filerevert-submit": "Вярнуць",
        "filerevert-success": "'''[[Media:$1|$1]]''' быў вернуты да [вэрсіі $4 ад $3, $2].",
        "filerevert-badversion": "Не існуе папярэдняй лякальнай вэрсіі гэтага файла з пазначанай датай.",
+       "filerevert-identical": "Цяперашняя вэрсія файлу ўжо ідэнтычная абранай.",
        "filedelete": "Выдаліць $1",
        "filedelete-legend": "Выдаліць файл",
        "filedelete-intro": "Вы выдаляеце файл '''[[Media:$1|$1]]''' з усёй яго гісторыяй.",
        "pageinfo-article-id": "Ідэнтыфікатар старонкі",
        "pageinfo-language": "Мова зьместу старонкі",
        "pageinfo-content-model": "Мадэль зьместу старонкі",
+       "pageinfo-content-model-change": "зьмяніць",
        "pageinfo-robot-policy": "Індэксацыя пашукавікамі",
        "pageinfo-robot-index": "Дазволеная",
        "pageinfo-robot-noindex": "Не дазволеная",
        "mediastatistics-summary": "Статыстыка тыпаў загружаных файлаў. Яна ўключае толькі актуальныя вэрсіі файлаў. Старыя і выдаленыя вэрсіі ня ўлічваюцца.",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 байт|$1 байты|$1 байтаў}} ($2; $3%)",
        "mediastatistics-bytespertype": "Агульны памер файлу для гэтага разьдзелу: {{PLURAL:$1|$1 байт|$1 байты|$1 байтаў}} ($2; $3%).",
+       "mediastatistics-allbytes": "Агульны памер усіх файлаў: {{PLURAL:$1|$1 байт|$1 байты|$1 байтаў}} ($2).",
        "mediastatistics-table-mimetype": "MIME-тып",
        "mediastatistics-table-extensions": "Магчымыя пашырэньні",
        "mediastatistics-table-count": "Колькасьць файлаў",
        "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
        "mw-widgets-titleinput-description-new-page": "старонка яшчэ не існуе",
        "mw-widgets-titleinput-description-redirect": "перанакіраваньне на $1",
+       "sessionmanager-tie": "Немагчыма выкарыстаць адначасова некалькі тыпаў аўтэнтыфікацыі: $1.",
+       "sessionprovider-generic": "$1 сэсіі",
+       "sessionprovider-mediawiki-session-cookiesessionprovider": "сэсіі на падставе файлаў-кукі",
+       "sessionprovider-nocookies": "Файлы-кукі могуць быць адключаныя. Упэўніцеся, што ў вас уключаныя файлы-кукі і пачніце спачатку.",
        "randomrootpage": "Выпадковая карэнная старонка",
        "log-action-filter-block": "Тып блякаваньня:",
        "log-action-filter-delete": "Тып выдаленьня:",
index f0c9999..a30b31a 100644 (file)
        "yourpasswordagain": "Паўтарыце пароль:",
        "createacct-yourpasswordagain": "Пацвердзіце пароль",
        "createacct-yourpasswordagain-ph": "Увядзіце пароль яшчэ раз",
-       "remembermypassword": "Памятаць мяне на гэтым камп'ютары (не даўжэй за $1 {{PLURAL:$1|дзень|дні|дзён}})",
        "userlogin-remembermypassword": "Заставацца ў сістэме",
        "userlogin-signwithsecure": "Выкарыстоўваць абароненае злучэнне",
        "cannotloginnow-title": "Зараз немагчыма ўвайсці",
        "passwordreset-emailerror-capture2": "Не ўдалося даслаць {{GENDER:$2|удзельніку|удзельніцы}} ліст электроннай поштай: $1 {{PLURAL:$3|Імя ўдзельніка і пароль|Спіс імён удзельнікаў і паролі}} паказаны ніжэй.",
        "passwordreset-nocaller": "Мусіць быць указана, хто выклікае",
        "passwordreset-nosuchcaller": "Аўтар выкліку не існуе: $1",
+       "passwordreset-ignored": "Скід пароля не быў апрацаваны. Магчыма, не настроены пастаўшчык?",
        "passwordreset-invalideamil": "Няслушны адрас электроннай пошты",
        "passwordreset-nodata": "Не былі пададзены ні імя ўдзельніка, ні адрас электроннай пошты",
        "changeemail": "Змяніць або выдаліць адрас электроннай пошты",
        "content-json-empty-object": "Пусты аб'ект",
        "content-json-empty-array": "Пусты масіў",
        "deprecated-self-close-category": "Старонкі з недапушчальнымі самазакрытымі HTML-тэгамі",
+       "deprecated-self-close-category-desc": "Старонка ўтрымлівае недапушчальныя самазакрытыя HTML-тэгі, такія як <code>&lt;b/></code> ці <code>&lt;span/></code>. Іх паводзіны ў хуткім часе будуць зменены ў адпаведнасці з спецыфікацыяй HTML5, таму іх ужыванне ў вікітэксце лічыцца састарэлым.",
        "duplicate-args-warning": "<strong>Увага:</strong> [[:$1]] выклікае [[:$2]] з больш чым адным значэннем для параметра \"$3\". Толькі апошняе з пададзеных значэнняў будзе ўжытае.",
        "duplicate-args-category": "Старонкі, якія выкарыстоўваюць задубляваныя параметры ў шаблонах",
        "duplicate-args-category-desc": "Старонка ўтрымлівае шаблоны з задубляванымі параметрамі, напрыклад, <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> або <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "badsig": "Недапушчальны зыходны тэкст подпісу; праверце тэгі HTML.",
        "badsiglength": "Занадта доўгі подпіс. Трэба, каб ён быў карацейшым за $1 {{PLURAL:$1|знак|знакаў}}.",
        "yourgender": "Пол:",
-       "gender-unknown": "Ð\9dÑ\8fвÑ\8bзнаÑ\87аны",
+       "gender-unknown": "Ð\97гадваÑ\8eÑ\87Ñ\8b Ð²Ð°Ñ\81, Ð¿Ñ\80агÑ\80ама Ð±Ñ\83дзе Ð¿Ð° Ð¼Ð°Ð³Ñ\87Ñ\8bмаÑ\81Ñ\86Ñ\96 Ñ\9eжÑ\8bваÑ\86Ñ\8c Ð³ÐµÐ½Ð´Ð°Ñ\80на-нейÑ\82Ñ\80алÑ\8cнÑ\8bÑ\8f Ñ\81ловы",
        "gender-male": "М",
        "gender-female": "Ж",
        "prefs-help-gender": "Неабавязкова: ужываецца дзеля пола-карэктнага звяртання з боку праграм. Гэтыя звесткі могуць стацца публічна вядомымі.",
        "grant-group-high-volume": "Выконваць вялікі аб'ём дзейнасці",
        "grant-group-customization": "Настройкі і перавагі",
        "grant-group-administration": "Выконваць адміністрацыйныя дзеянні",
+       "grant-group-private-information": "Доступ да прыватных звестак пра вас",
        "grant-group-other": "Розная актыўнасць",
        "grant-blockusers": "Блакаваць і разблакаваць удзельнікаў",
        "grant-createaccount": "Ствараць уліковыя запісы",
        "grant-highvolume": "Вялікі аб'ём рэдагавання",
        "grant-oversight": "Утойваць удзельнікаў і версіі старонак",
        "grant-patrol": "Патруляваць змены старонак",
+       "grant-privateinfo": "Доступ да прыватных звестак",
        "grant-protect": "Ахоўваць і здымаць ахову старонак",
        "grant-rollback": "Адкатваць змяненні старонак",
        "grant-sendemail": "Адпраўляць электронную пошту іншым удзельнікам",
        "rightslogtext": "Журнал змяненняў у дазволах, прыпісаных удзельнікам.",
        "action-read": "чытаць гэтую старонку",
        "action-edit": "правіць гэтую старонку",
-       "action-createpage": "ствараць старонкі",
-       "action-createtalk": "ствараць размоўныя старонкі",
+       "action-createpage": "стварыць гэту старонку",
+       "action-createtalk": "стварыць гэту размоўную старонку",
        "action-createaccount": "ствараць гэты рахунак удзельніка",
        "action-autocreateaccount": "аўтаматычна ствараць гэты вонкавы ўліковы запіс удзельніка",
        "action-history": "глядзець гісторыю гэтай старонкі",
        "action-applychangetags": "прымяняць біркі з сваімі праўкамі",
        "action-changetags": "дадаваць і выдаляць адвольныя біркі да асобных версій і запісаў у журнале падзей",
        "action-deletechangetags": "выдаляць біркі з базы даных",
+       "action-purge": "ачысціць кэш гэтай старонкі",
        "nchanges": "$1 {{PLURAL:$1|змена|змены|змен}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|з часу апошняга наведвання}}",
        "enhancedrc-history": "гісторыя",
        "file-thumbnail-no": "Назва файла пачынаецца з <strong>$1</strong>.\nТак можа называцца выява зменшанага памеру ''(драбніца)''.\nКалі гэтая выява сапраўды запісаная ў найлепшым разрозненні, якое ёсць, то ўкладайце яе, а іначай лепей памяняць назву файла.",
        "fileexists-forbidden": "Файл з такой назвай ужо ёсць, і нельга запісаць паўзверх яго. Калі вы жадаеце абавязкова ўкласці свой файл, то выберыце новую назву. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "У агульным сховішчы ўжо існуе файл з такою назвай.\nКалі вы жадаеце ўсё ж укласці свой файл, паўтарыце працэдуру ўкладання, але з іншай назвай. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Укладанне з'яўляецца дакладнай копіяй бягучай версіі <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Укладанне з'яўляецца дакладнай копіяй {{PLURAL:$2|старой версіі|старых версій}} файла <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Гэты файл з'яўляецца дублікатам наступн{{PLURAL:$1|ага файла|ых файлаў}}:",
        "file-deleted-duplicate": "Файл, падобны да гэтага ([[:$1]]), быў сцёрты некалі раней. Трэба праверыць гісторыю таго файла перад тым, як укладваць яго нанова.",
        "file-deleted-duplicate-notitle": "Файл, ідэнтычны гэтаму, быў сцёрты раней, а назва файла заглушана.\nВы мусіце спытаць каго-небудзь з магчымасцю бачыць заглушаныя звесткі па файлах пераглядзець сітуацыю перад тым, як укладваць яго нанова.",
        "htmlform-cloner-delete": "Сцерці",
        "htmlform-cloner-required": "Неабходна хаця б адно значэнне.",
        "htmlform-title-badnamespace": "[[:$1]] не ў прасторы назваў \"{{ns:$2}}\".",
+       "htmlform-title-not-creatable": "\"$1\" - немагчымы загаловак для старонкі",
        "htmlform-title-not-exists": "$1 не існуе.",
        "htmlform-user-not-exists": "<strong>$1</strong> не існуе.",
+       "htmlform-user-not-valid": "<strong>$1</strong> - недапушчальная назва уліковага запісу.",
        "sqlite-has-fts": "$1 з падтрымкай поўна-тэкставага пошуку",
        "sqlite-no-fts": "$1 без падтрымкі поўна-тэкставага пошуку",
        "logentry-delete-delete": "$1 {{GENDER:$2|сцёр|сцёрла}} старонку $3",
        "logentry-block-block": "$1 заблакірава{{GENDER:$2|ў|ла}} {{GENDER:$4|$3}} на перыяд $5 $6",
        "logentry-block-unblock": "$1 {{GENDER:$2|разблакаваў|разблакавала}} {{GENDER:$4|$3}}",
        "logentry-block-reblock": "$1 {{GENDER:$2|памяняў|памяняла}} настройкі блакіроўкі {{GENDER:$4|$3}} на перыяд $5 $6",
+       "logentry-suppress-block": "$1 {{GENDER:$2|заблакіраваў|заблакіравала}} {{GENDER:$4|$3}} на перыяд $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|памяняў|памяняла}} параметры блакіроўкі {{GENDER:$4|$3}} на перыяд $5 $6",
+       "logentry-import-upload": "$1 {{GENDER:$2|імпартаваў|імпартавала}} $3 праз укладанне файла",
+       "logentry-import-upload-details": "$1 {{GENDER:$2|імпартаваў|імпартавала}} $3 праз укладанне файла ($4 {{PLURAL:$4|версія|версіі|версій}})",
        "logentry-import-interwiki": "$1 {{GENDER:$2|імпартаваў|імпартавала}} $3 з іншай вікі",
+       "logentry-import-interwiki-details": "$1 {{GENDER:$2|імпартаваў|імпартавала}} $3 з $5 ($4 {{PLURAL:$4|версія|версіі|версій}})",
        "logentry-move-move": "$1 {{GENDER:$2|перанёс|перанесла}} старонку $3 у $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|перанёс|перанесла}} старонку $3 у $4, не пакінуўшы перасылкі",
        "logentry-move-move_redir": "$1 {{GENDER:$2|перанёс|перанесла}} старонку $3 у $4 па-над перасылкаю",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|уклаў|уклала}} новую версію $3",
        "logentry-upload-revert": "$1 {{GENDER:$2|уклаў|уклала}} $3",
        "log-name-managetags": "Журнал кіравання біркамі",
+       "logentry-managetags-create": "$1 {{GENDER:$2|стварыў|стварыла}} бірку \"$4\"",
+       "logentry-managetags-delete": "$1 {{GENDER:$2|выдаліў|выдаліла}} бірку \"$4\" (выдалена з $5 {{PLURAL:$5|версіі ці запісу ў журнале|версій і/або запісаў у журнале}})",
+       "logentry-managetags-activate": "$1 {{GENDER:$2|актываваў|актывавала}} бірку \"$4\" для выкарыстання ўдзельнікамі і робатамі",
+       "logentry-managetags-deactivate": "$1 {{GENDER:$2|дэактываваў|дэактывавала}} бірку \"$4\" для выкарыстання ўдзельнікамі і робатамі",
        "log-name-tag": "Журнал бірак",
+       "log-description-tag": "На гэтай старонцы паказана, калі ўдзельнікі дадавалі ці выдалялі [[Special:Tags|біркі]] ў асобных версіях ці запісах журнала. Журнал не захоўвае дзеянні з біркамі, калі яны былі часткай рэдагавання, выдалення ці падобных дзеянняў.",
+       "logentry-tag-update-add-revision": "$1 {{GENDER:$2|дадаў|дадала}} {{PLURAL:$7|1=бірку|біркі}} $6 да версіі $4 старонкі $3",
+       "logentry-tag-update-add-logentry": "$1 {{GENDER:$2|дадаў|дадала}} {{PLURAL:$7|1=бірку|біркі}} $6 да запісу ў журнале $5 старонкі $3",
+       "logentry-tag-update-remove-revision": "$1 {{GENDER:$2|выдаліў|выдаліла}} {{PLURAL:$9|1=бірку|біркі}} $8 з версіі $4 старонкі $3",
        "rightsnone": "(няма)",
        "revdelete-summary": "тлумачэнне праўкі",
        "feedback-adding": "Даданне водгуку на старонку…",
index 219df61..3d9a515 100644 (file)
@@ -28,7 +28,9 @@
                        "Sayma Jahan",
                        "Macofe",
                        "Bodhisattwa",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "আজিজ",
+                       "Kayser Ahmad"
                ]
        },
        "tog-underline": "সংযোগগুলির নিচে দাগ দেখানো হোক:",
        "missingarticle-rev": "(সংস্করণ#: $1)",
        "missingarticle-diff": "(পার্থক্য: $1, $2)",
        "readonly_lag": "ডাটাবেজ স্বয়ংক্রিয়ভাবে বন্ধ করে দেয়া হয়েছে, যাতে অধীন ডাটাবেজ সার্ভারগুলি প্রধান ডাটাবেজ সার্ভারের অবস্থায় আসতে পারে।",
+       "nonwrite-api-promise-error": "'Promise-Non-Write-API-Action' HTTP শিরলেখে পাঠানো হয়েছিল কিন্তু অনুরোধটি একটি API লিখন মডিউলে ছিল।",
        "internalerror": "আভ্যন্তরীণ ত্রুটি",
        "internalerror_info": "আভ্যন্তরীণ ত্রুটি: $1",
        "internalerror-fatal-exception": "\"$1\" ধরনের মারাত্মক ব্যতিক্রম",
        "yourpasswordagain": "পাসওয়ার্ড আবার লিখুন:",
        "createacct-yourpasswordagain": "পাসওয়ার্ড নিশ্চিত করুন",
        "createacct-yourpasswordagain-ph": "আবারও পাসওয়ার্ড লিখুন",
-       "remembermypassword": "এই ব্রাউজারে আমার প্রবেশ মনে রাখা হোক (সর্বোচ্চ $1 {{PLURAL:$1|দিনের}} জন্য)",
        "userlogin-remembermypassword": "আমাকে প্রবেশ অবস্থায় রাখো",
        "userlogin-signwithsecure": "নিরাপদ সংযোগ ব্যবহার করুন",
+       "cannotlogin-title": "প্রবেশ করতে পারবেন না",
        "cannotloginnow-title": "এখন প্রবেশ করা যাবে না",
        "cannotloginnow-text": "$1 ব্যবহার করার সময় প্রবেশ করা সম্ভব নয়।",
        "yourdomainname": "আপনার ডোমেইন:",
        "createacct-email-ph": "আপনার ইমেইল ঠিকানা যোগ করুন",
        "createacct-another-email-ph": "আপনার ইমেইল ঠিকানা প্রবেশ করান",
        "createaccountmail": "একটি র‌্যান্ডম পাসওয়ার্ড নির্বাচন করুন এবং নির্ধারিত ইমেইল ঠিকানায় পাঠিয়ে দিন",
+       "createaccountmail-help": "পাসওয়ার্ড জানা ছাড়াই অন্য ব্যক্তির জন্য অ্যাকাউন্ট তৈরি করতে ব্যবহার করা যেতে পারে।",
        "createacct-realname": "আসল নাম (ঐচ্ছিক)",
        "createaccountreason": "কারণ:",
        "createacct-reason": "কারণ",
        "nocookiesnew": "ব্যবহারকারীর অ্যাকাউন্টটি সৃষ্টি করা হয়েছে, কিন্তু আপনি এখনও অ্যাকাউন্টে প্রবেশ করেননি। {{SITENAME}}-তে কুকি ব্যবহার করে ব্যবহারকারীদের অ্যাকাউন্টে প্রবেশ করানো হয়। আপনার ব্রাউজারে কুকিগুলি নিষ্ক্রিয় করা আছে। অনুগ্রহ করে কুকিগুলি সক্রিয় করুন এবং আপনার নতুন ব্যবহারকারী নাম ও পাসওয়ার্ড ব্যবহার করে অ্যাকাউন্টে প্রবেশ করুন।",
        "nocookieslogin": "ব্যবহারকারীদের প্রবেশ সম্পন্ন করতে {{SITENAME}} কুকি ব্যবহার করে। আপনার ব্রাউজারে কুকি নিষ্ক্রিয় করা আছে। কুকি চালু করে আবার চেষ্টা করুন।",
        "nocookiesfornew": "ব্যবহারকারীর অ্যাকাউন্ট তৈরি হয়নি, কারণ এর উৎস সম্পর্কে আমরা নিশ্চিত নই।\nনিশ্চিত করুন আপনার কুকি সক্রিয় রয়েছে, পাতাটি পুনরায় লোড করে আবার চেষ্টা করুন।",
+       "createacct-loginerror": "অ্যাকাউন্ট সফলভাবে তৈরি করা হয়েছে কিন্তু আপনি স্বয়ংক্রিয়ভাবে প্রবেশ করতে পারবেন না। দয়া করে [[Special:UserLogin|ম্যানুয়াল প্রবেশ]] করতে এগিয়ে যান।",
        "noname": "আপনি সঠিক ব্যবহারকারী নাম নির্দিষ্ট করেননি।",
        "loginsuccesstitle": "প্রবেশ করেছেন",
        "loginsuccess": "<strong>আপনি এইমাত্র \"$1\" নামে {{SITENAME}}-তে প্রবেশ করেছেন।</strong>",
        "grant-editprotected": "সংরক্ষিত পাতা সম্পাদনা করুন",
        "grant-privateinfo": "ব্যক্তিগত তথ্যে প্রবেশাধিকার",
        "grant-sendemail": "অন্য ব্যবহারকারীকে ইমেইল পাঠান",
+       "grant-uploadeditmovefile": "ফাইল আপলোড, প্রতিস্থাপন এবং স্থানান্তর",
        "grant-uploadfile": "নতুন ফাইল আপলোড করুন",
        "grant-basic": "মৌলিক অধিকার",
        "grant-viewdeleted": "অপসারিত ফাইল ও পাতাগুলি দেখুন",
        "action-createpage": "এই পাতাটি তৈরি করার",
        "action-createtalk": "এই আলাপ পাতাটি তৈরি করার",
        "action-createaccount": "এই ব্যবহারকারী একাউন্টটি তৈরি করার",
+       "action-autocreateaccount": "স্বয়ংক্রিয়ভাবে এই বাহ্যিক ব্যবহারকারী অ্যাকাউন্ট তৈরি করার",
        "action-history": "এই পাতার ইতিহাস দেখার",
        "action-minoredit": "এই সম্পাদনাটি অনুল্লেখ্য হিসেবে চিহ্নিত করার",
        "action-move": "পাতাটি সরিয়ে ফেলুন",
        "action-managechangetags": "ট্যাগ তৈরি ও সক্রিয়/নিষ্ক্রিয়",
        "action-applychangetags": "আপনার পরিবর্তনগুলোর সাথে ট্যাগ সংযোজন করুন",
        "action-changetags": "নির্দিষ্ট সংস্করণ এবং দীর্ঘ সম্পাদনাগুলোতে ট্যাগ সংযোজন ও অপসারণ করুন",
+       "action-deletechangetags": "ডাটাবেজ থেকে ট্যাগ অপসরণ করার",
        "action-purge": "এই পাতা হালনাগাদ করার",
        "nchanges": "$1টি {{PLURAL:$1|পরিবর্তন}}",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|সর্বশেষ প্রদর্শনের পর}} $1টি",
        "file-thumbnail-no": "ফাইলের নামটি <strong>$1</strong> দিয়ে শুরু হয়েছে।\nমনে হচ্ছে এটি একটি সংকুচিত আকারের ছবি  ''(থাম্বনেইল)''।\nআপনার কাছে যদি পূর্ণ রেজোলিউশনের ছবিটি থাকে, তবে সেটি আপলোড করুন, নতুবা অনুগ্রহ করে ফাইলের নামটি পরিবর্তন করুন।",
        "fileexists-forbidden": "এই নামের একটি ফাইল ইতিমধ্যেই বিদ্যমান, এবং এটি প্রতিস্থাপনযোগ্য নয়।\nআপনি যদি এখনো ফাইলটি আপলোড করতে চান, তবে অনুগ্রহপূর্বক পেছনে গিয়ে একটি নতুন নামে ফাইলটি আপলোড করুন।\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "অংশীদারী ফাইল ভাণ্ডারে এই নামের একটি ফাইল ইতিমধ্যেই বিদ্যমান।\nআপনি যদি এখনো ফাইলটি আপলোড করতে চান, তবে অনুগ্রহপূর্বক পেছনে গিয়ে একটি নতুন নামে ফাইলটি আপলোড করুন।[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "আপলোডটি <strong>[[:$1]]</strong>-এর বর্তমান সংস্করণের হুবহু প্রতিলিপি।",
+       "fileexists-duplicate-version": "এই আপলোডটি <strong>[[:$1]]</strong>-এর একটি {{PLURAL:$2|পুরনো সংস্করণের}} হুবহু প্রতিলিপি।",
        "file-exists-duplicate": "এই ফাইলটি নিচের {{PLURAL:$1|ফাইল|ফাইলগুলির}} অনুলিপি:",
        "file-deleted-duplicate": "এই ফাইলটির মত একটি ফাইল ([[:$1]]) পূর্বে অপসারণ করা হয়েছে।\nপুনরায় আপলোড করার পূর্বে আপনার উচিত আগের ফাইলটির অপসারণের কারণ জানা।",
        "uploadwarning": "আপলোড সতর্কবাণী",
        "uploadscripted": "এই ফাইলে এমন HTML বা স্ক্রিপ্ট কোড আছে যা একটি ওয়েব ব্রাউজার ভুল বুঝতে পারে।",
        "uploaded-script-svg": "আপলোডকৃত SVG ফাইলে স্ক্রিপ্টযোগ্য উপাদান \"$1\" পাওয়া গেছে।",
        "uploaded-hostile-svg": "আপলোড করা SVG ফাইলের শৈলী উপাদানে অনিরাপদ সিএসএস পাওয়া গেছে।",
+       "uploaded-href-unsafe-target-svg": "অনিরাপদ উপাত্তে href পাওয়া গেছে: আপলোডকৃত SVG ফাইলে URI লক্ষ্য ছিল <code>&lt;$1 $2=\"$3\"&gt;</code>।",
        "uploaded-image-filter-svg": "আপলোডকৃত SVG ফাইলে URL: <code>&lt;$1 $2=\"$3\"&gt;</code> সহ ছবি পরিশোধক পাওয়া গেছে।",
        "uploadscriptednamespace": "এই SVG ফাইলে অবৈধ নামস্থান \"$1\" রয়েছে",
        "uploadinvalidxml": "আপলোডকৃত ফাইলে XML পার্স করা যাবে না।",
        "upload-form-label-own-work": "এটি আমার নিজের কাজ",
        "upload-form-label-infoform-categories": "বিষয়শ্রেণীসমূহ",
        "upload-form-label-infoform-date": "তারিখ",
+       "upload-form-label-own-work-message-generic-local": "আমি নিশ্চিত করছি যে আমি {{SITENAME}}-এর পরিষেবা এবং লাইসেন্সকরণ নীতির শর্তাবলী অনুসরণ করে এই ফাইল আপলোড করছি।",
        "upload-form-label-not-own-work-local-generic-local": "এছাড়াও আপনি [[Special:Upload|ডিফল্ট আপলোডের পাতা]] চেষ্টা করতে পারেন।",
        "upload-form-label-not-own-work-local-generic-foreign": "এছাড়াও আপনি [[Special:Upload|{{SITENAME}}-এর আপলোডের পাতা]] ব্যবহার করার চেষ্টা করতে পারেন, যদি এই ফাইলটি তাদের নীতিমালা অধীনে সেখানে আপলোড করা যায়।",
        "backend-fail-stream": "\"$1\" ফাইলের স্ট্রিম দেখানো যাচ্ছে না।",
        "backend-fail-read": "$1 ফাইলটি ওপেন করা যাচ্ছে না।",
        "backend-fail-create": "$1 ফাইলটি তৈরী করা যাচ্ছে না।",
        "backend-fail-maxsize": "\"$1\" ফাইলে লেখা যাচ্ছে না কারণ এটি {{PLURAL:$2|এক বাইট|$2 বাইট}} থেকে বড়।",
-       "backend-fail-readonly": "\"$1\" à¦¸à§\8dà¦\9fà§\8bরà§\87à¦\9c à¦¬à§\8dযাà¦\95à¦\8fনà§\8dড à¦¥à§\87à¦\95à§\87 à¦¬à¦°à§\8dতমানà§\87 à¦²à§\87à¦\96া à¦¯à¦¾à¦\9aà§\8dà¦\9bà§\87 à¦¨à¦¾à¥¤ à¦\95ারণ: \"''$2''\"",
+       "backend-fail-readonly": "\"$1\" à¦¸à§\8dà¦\9fà§\8bরà§\87à¦\9c à¦¬à§\8dযাà¦\95à¦\8fনà§\8dড à¦¥à§\87à¦\95à§\87 à¦¬à¦°à§\8dতমানà§\87 à¦¶à§\81ধà§\81-পঠনà§\87 à¦°à¦¯à¦¼à§\87à¦\9bà§\87। à¦ªà§\8dরদতà§\8dত à¦\95ারণ: <em>$2</em>",
        "backend-fail-synced": "\"$1\" ফাইলটি ইন্টারনাল স্টোরেজ ব্যকএন্ডের সাথে অসামঞ্জস্যপূর্ণ",
        "backend-fail-connect": "স্টোরেজ ব্যাকেন্ড \"$1\" এর সাথে যোগাযোগ করা যাচ্ছে না।",
        "backend-fail-internal": "\"$1\" স্টোরেজ ব্যাকেন্ডে কোনো অজানা ত্রুটি হয়েছে।",
        "uploadstash-errclear": "ফাইলগুলো পরিষ্কারকরণ ব্যর্থ হয়েছে।",
        "uploadstash-refresh": "ফাইলের তালিকা রিফ্রেশ করুন",
        "uploadstash-thumbnail": "থাম্বনেইল দেখুন",
+       "uploadstash-exception": "স্টাসে আপলোড সঞ্চয় করা যায়নি ($1): \"$2\"।",
        "invalid-chunk-offset": "ত্রুটিপূর্ণ চাংক অফসেট",
        "img-auth-accessdenied": "প্রবেশাধিকার নাই",
        "img-auth-nopathinfo": "PATH_INFO পাওয়া যাচ্ছে না।\nআপনার সার্ভার থেকে এই তথ্য পাঠানোর জন্য কনফিগার করা হয়নি।\nএটি হয়তো CGI ভিত্তিক এবং img_auth সমর্থন করে না।\nবিস্তারিত দেখুন https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization।",
        "apisandbox-fullscreen": "প্যানেল সম্প্রসারণ করুন",
        "apisandbox-fullscreen-tooltip": "ব্রাউজারের উইন্ডো পূরণ করতে খেলাঘরের প্যানেল প্রসারিত করুন।",
        "apisandbox-unfullscreen": "পাতা দেখাও",
+       "apisandbox-unfullscreen-tooltip": "খেলাঘরের প্যানেল হ্রাস করুন, তাহলে মিডিয়াউইকি পরিভ্রমণ করার সংযোগগুলি পাওয়া যাবে।",
        "apisandbox-submit": "অনুরোধ রাখুন",
        "apisandbox-reset": "পরিস্কার",
        "apisandbox-retry": "পুনঃচেষ্টা করুন",
        "listgrouprights-removegroup-self-all": "নিজের অ্যাকাউন্ট থেকে সকল দল অপসারণ",
        "listgrouprights-namespaceprotection-header": "নামস্থান নিষেধাজ্ঞাসমূহ",
        "listgrouprights-namespaceprotection-namespace": "নামস্থান",
+       "listgrants": "কার্যভার",
+       "listgrants-summary": "নিম্নে ব্যবহারকারী অধিকারের সাথে যুক্ত প্রবেশাধিকারসহ তাদের কার্যভারের একটি তালিকা দেয়া হয়েছে। ব্যবহারকারীরা তাদের অ্যাকাউন্ট ব্যবহার করতে অ্যাপ্লিকেশনকে অনুমোদন দিতে পারে, কিন্তু কার্যভারের উপর ভিত্তি করে সীমিত অনুমতি ব্যবহারকারীরা অ্যাপ্লিকেশনকে দিতে পারবেন। মূলত, একটি অ্যাপ্লিকেশন একজন ব্যবহারকারীর দেয়া অধিকারের অতিরিক্ত অধিকার ব্যবহার করতে পারবে না। পৃথক অধিকার সম্পর্কে [[{{MediaWiki:Listgrouprights-helppage}}|অতিরিক্ত তথ্য]] দেখুন।",
+       "listgrants-grant": "কার্যভার",
        "listgrants-rights": "অধিকারসমূহ",
        "trackingcategories": "বিষয়শ্রেণীসমূহ অনুসরণ করা হচ্ছে",
        "trackingcategories-msg": "বিষয়শ্রেণী অনুসরণ করা হচ্ছে",
        "restricted-displaytitle-ignored": "উপেক্ষিত প্রদর্শন শিরোনামসহ পাতা",
        "restricted-displaytitle-ignored-desc": "পাতাটি একটি <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> উপেক্ষা করেছে কারণ এটা পাতাটির আসল শিরোনামের সাথে সমতুল্য নয়।",
        "broken-file-category-desc": "এই পাতায় একটি ভাঙ্গা ফাইলের লিঙ্ক রয়েছে (একটি ফাইল এম্বেড করার জন্য একটি লিঙ্ক যখন ফাইলটির অস্তিত্ব নেই)",
+       "hidden-category-category-desc": "এই বিষয়শ্রেণীটির পাতার বিষয়বস্তুর মধ্যে <code><nowiki>__HIDDENCAT__</nowiki></code> উপস্থিত রয়েছে, যা পূর্ব-নির্ধারিতভাবে পাতার বিষয়শ্রেণীর সংযোগে দেখায় না।",
        "trackingcategories-nodesc": "কোন বর্ণনা নেই।",
        "trackingcategories-disabled": "বিষয়শ্রেণীটি বিকল",
        "mailnologin": "প্রাপকের ঠিকানা নেই",
        "rollbacklinkcount": "$1টি {{PLURAL:$1|সম্পাদনা}} রোলব্যাক করুন",
        "rollbacklinkcount-morethan": "$1টির বেশি {{PLURAL:$1|সম্পাদনা}} রোলব্যাক করুন",
        "rollbackfailed": "রোলব্যাক ব্যর্থ",
+       "rollback-missingparam": "অনুরোধে প্রয়োজনীয় প্যারামিটারগুলি অনুপস্থিত।",
+       "rollback-missingrevision": "সংশোধনের উপাত্ত লোড করতে অক্ষম।",
        "cantrollback": "পূর্বের সংস্করণে ফেরত যাওয়া সম্ভব হল না, সর্বশেষ সম্পাদনাকারী এই নিবন্ধটির একমাত্র লেখক।",
        "alreadyrolled": "[[User:$2|$2]] ([[User talk:$2|talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) দ্বারা সম্পাদিত সর্বশেষ [[:$1]] সম্পাদনাটি পুনর্বহাল করা যাচ্ছে না;\nঅন্য কোন ব্যবহারকারী এই পাতা ইতিমধ্যে সম্পাদনা বা পুনর্বহাল করেছেন।\n\nএই পাতায় সর্বোশেষে [[User:$3|$3]] ([[User talk:$3|talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) দ্বারা সম্পাদিত।",
        "editcomment": "সম্পাদনা সারাংশ ছিল: \"''$1''\"।",
        "undeletedrevisions": "{{PLURAL:$1|১টি সংশোধন|$1টি সংশোধন}} পুনরুদ্ধার করা হয়েছে",
        "undeletedrevisions-files": "{{PLURAL:$1|১টি সংশোধন|$1টি সংশোধন}} এবং {{PLURAL:$2|১টি ফাইল|$2টি ফাইল}} পুনরুদ্ধার করা হয়েছে",
        "undeletedfiles": "{{PLURAL:$1|১টি ফাইল|$1টি ফাইল}} পুনরুদ্ধার করা হয়েছে",
-       "cannotundelete": "মà§\81à¦\9bà§\87 à¦«à§\87লা à¦¬à¦¾à¦¤à¦¿à¦² করা যায়নি:\n$1",
+       "cannotundelete": "à¦\95িà¦\9bà§\81 à¦¬à¦¾ à¦¸à¦¬ à¦ªà§\81নরà§\81দà§\8dধার করা যায়নি:\n$1",
        "undeletedpage": "'''$1 পুনরুদ্ধার করা হয়েছে'''\n\nসাম্প্রতিক মুছে ফেলা ও পুনরুদ্ধারের ঘটনাগুলির জন্য [[Special:Log/delete|অবলুপ্তি লগ]] দেখুন।",
        "undelete-header": "সাম্প্রতিক সময়ে মুছে ফেলা পাতাগুলি দেখতে [[Special:Log/delete|অবলুপ্তি লগ]] দেখুন।",
        "undelete-search-title": "অপসারিত পাতা অনুসন্ধান করো",
        "sp-contributions-newbies-sub": "নতুন অ্যাকাউন্টের জন্য",
        "sp-contributions-newbies-title": "নতুন অ্যাকাউন্টের ব্যবহারকারী অবদান",
        "sp-contributions-blocklog": "বাধা দানের লগ",
-       "sp-contributions-suppresslog": "মà§\81à¦\9bà§\87 à¦«à§\87লা à¦¬à§\8dযবহারà¦\95ারà§\80 অবদান",
-       "sp-contributions-deleted": "মুছে ফেলা ব্যবহারকারী অবদান",
+       "sp-contributions-suppresslog": "à¦\97à§\8bপনà¦\95à§\83ত {{GENDER:$1|বà§\8dযবহারà¦\95ারà§\80র}} অবদান",
+       "sp-contributions-deleted": "মুছে ফেলা {{GENDER:$1|ব্যবহারকারীর}} অবদান",
        "sp-contributions-uploads": "আপলোডসমূহ",
        "sp-contributions-logs": "লগসমূহ",
        "sp-contributions-talk": "আলোচনা",
        "move-page-legend": "পাতা স্থানান্তর",
        "movepagetext": "নিচের ফর্মটি ব্যবহার করে একটি পাতার শিরোনাম পরিবর্তন করা যাবে, এবং সেই সাথে নতুন শিরোনামে এর সমগ্র ইতিহাস স্থানান্তর করা যাবে।\nপুরনো শিরোনামটি নতুন শিরোনামটির প্রতি একটি পুনর্নির্দেশনা ধারণ করবে।\nযেসমস্ত পুনর্নির্দেশনা পুরনো শিরোনামটির দিকে নির্দেশ করছিল, সেগুলি স্বয়ংক্রিয়ভাবে হালনাগাদ করতে পারবেন।\nযদি তা না চান, তবে [[Special:DoubleRedirects|দ্বি-পুনর্নির্দেশনা]] বা [[Special:BrokenRedirects|অচল পুনর্নির্দেশনাগুলি]] পরীক্ষা করে দেখতে ভুলবেন না।\nসংযোগগুলি যাতে তাদের লক্ষ্যে পৌঁছায়, তা নিশ্চিত করার দায়িত্ব আপনার।\n\nলক্ষ্য করুন যে যদি নতুন শিরোনামে ইতিমধ্যেই একটি পাতা থেকে থাকে, তবে উৎস পাতাটি সেই শিরোনামে স্থানান্তর করা হবে <strong>না</strong>, যদি না নতুন শিরোনামের পাতাটি খালি থাকে বা একটি পুননির্দেশনা হয় এবং এর কোন অতীত সম্পাদনা ইতিহাস না থাকে।\nঅর্থাৎ আপনি ভুল করে নাম পরিবর্তন করলে সহজেই পুরনো নামে ফেরত যেতে পারবেন, কিন্তু ইতিমধ্যে বিদ্যমান কোন পাতার উপরে লিখতে পারবেন না।\n\n<strong>টীকা:</strong>\nকোন জনপ্রিয় পাতার ক্ষেত্রে এই পরিবর্তনটি খুবই আকস্মিক হতে পারে; অগ্রসর হবার আগে এই কাজটির ফলাফল কী হতে পারে, সে ব্যাপারে অনুগ্রহ করে নিশ্চিত হোন।",
        "movepagetext-noredirectfixer": "নিচের ফর্মটি ব্যবহার করে একটি পাতার শিরোনাম পরিবর্তন করা যাবে, এবং সেই সাথে নতুন শিরোনামে এর সমগ্র ইতিহাস স্থানান্তর করা যাবে।\nপুরনো শিরোনামটি নতুন শিরোনামটির প্রতি একটি পুনর্নির্দেশনা ধারণ করবে।\n[[Special:DoubleRedirects|দ্বি-পুনর্নির্দেশনা]] বা [[Special:BrokenRedirects|অচল পুনর্নির্দেশনাগুলি]] পরীক্ষা করে দেখতে ভুলবেন না।\nসংযোগগুলি যাতে তাদের লক্ষ্যে পৌঁছায়, তা নিশ্চিত করার দায়িত্ব আপনার।\n\nলক্ষ্য করুন যে যদি নতুন শিরোনামে ইতিমধ্যেই একটি পাতা থেকে থাকে, তবে উৎস পাতাটি সেই শিরোনামে স্থানান্তর করা হবে <strong>না</strong>, যদি না নতুন শিরোনামের পাতাটি খালি থাকে বা একটি পুননির্দেশনা হয় এবং এর কোন অতীত সম্পাদনা ইতিহাস না থাকে। \nঅর্থাৎ আপনি ভুল করে নাম পরিবর্তন করলে সহজেই পুরনো নামে ফেরত যেতে পারবেন, কিন্তু ইতিমধ্যে বিদ্যমান কোন পাতার উপরে লিখতে পারবেন না।\n\n<strong>টীকা:</strong>\nকোন জনপ্রিয় পাতার ক্ষেত্রে এই পরিবর্তনটি খুবই আকস্মিক হতে পারে;\nঅগ্রসর হবার আগে এই কাজটির ফলাফল কী হতে পারে, সে ব্যাপারে অনুগ্রহ করে নিশ্চিত হোন।",
-       "movepagetalktext": "পাতাà¦\9fির à¦¸à¦¾à¦¥à§\87 à¦¸à¦¾à¦¥à§\87 à¦¸à¦\82শà§\8dলিষà§\8dà¦\9f à¦\86লà§\8bà¦\9aনা à¦ªà¦¾à¦¤à¦¾à¦\9fিà¦\93 à¦¸à§\8dবয়à¦\82à¦\95à§\8dরিয়ভাবà§\87 à¦¸à¦°à¦¾à¦¨à§\8b à¦¹à¦¬à§\87 '''যদি à¦¨à¦¾:'''\n*à¦\96ালি à¦¨à¦¯à¦¼ à¦\8fমন à¦\8fà¦\95à¦\9fি à¦\86লাপ à¦ªà¦¾à¦¤à¦¾ à¦¨à¦¤à§\81ন à¦¶à¦¿à¦°à§\8bনামà¦\9fির à¦\85ধà§\80নà§\87 à¦\87তিমধà§\8dযà§\87à¦\87 à¦¬à¦¿à¦¦à§\8dযমান à¦¥à¦¾à¦\95à§\87, à¦\85থবা\n*à¦\86পনি à¦¨à¦¿à¦\9aà§\87র à¦¬à¦¾à¦\95à§\8dসà¦\9fি à¦¥à§\87à¦\95à§\87 à¦\9fিà¦\95 à¦¸à¦°à¦¿à¦¯à¦¼à§\87 à¦¨à¦¿à¦¤à§\87 à¦ªà¦¾à¦°à§\87ন।\n\nএসব ক্ষেত্রে আপনি চাইলে নিজের হাতে পাতাটিকে সরাতে বা একত্রীকরণ করতে পারেন।",
+       "movepagetalktext": "à¦\86পনি à¦¯à¦¦à¦¿ à¦\8fà¦\87 à¦¬à¦¾à¦\95à§\8dসà§\87 à¦\9fিà¦\95 à¦¦à§\87ন, à¦¤à¦¾à¦¹à¦²à§\87 à¦ªà¦¾à¦¤à¦¾à¦\9fির à¦¸à¦¾à¦¥à§\87 à¦¸à¦\82শà§\8dলিষà§\8dà¦\9f à¦\86লà§\8bà¦\9aনা à¦ªà¦¾à¦¤à¦¾à¦\9fিà¦\93 à¦¸à§\8dবয়à¦\82à¦\95à§\8dরিয়ভাবà§\87 à¦¨à¦¤à§\81ন à¦¶à¦¿à¦°à§\8bনামà§\87 à¦¸à¦°à¦¾à¦¨à§\8b à¦¹à¦¬à§\87 à¦¯à¦¦à¦¿ à¦¨à¦¾ à¦¨à¦¤à§\81ন à¦¶à¦¿à¦°à§\8bনামà¦\9fির à¦\85ধà§\80নà§\87 à¦\87তিমধà§\8dযà§\87à¦\87 à¦\8fà¦\95à¦\9fি à¦¬à¦¿à¦¦à§\8dযমান à¦¥à¦¾à¦\95à§\87।\n\nএসব ক্ষেত্রে আপনি চাইলে নিজের হাতে পাতাটিকে সরাতে বা একত্রীকরণ করতে পারেন।",
        "moveuserpage-warning": "'''সতর্কতা:''' আপনি একটি ব্যবহারকারী পাতা স্থানান্তর করছেন। অনুগ্রহ করে লক্ষ্য করুন যে এর মাধ্যমে কেবলমাত্র পাতাটি স্থানান্তর হবে, কিন্তু পাতার নাম পরিবর্তন হবে ''না''।",
        "movecategorypage-warning": "<strong>সতর্কীকরণ:</strong> আপনি একটি বিষয়শ্রেণীর পাতা স্থানান্তর করতে চলেছেন। দয়া করে মনে রাখবেন যে এতে শুধুমাত্র পাতাটি স্থানান্তরিত হবে এবং পুরাতন বিষয়শ্রেণীতে থাকা কোন পাতা নতুনটিতে পুনঃশ্রেণীকরণ করা হবে <em>না</em>।",
        "movenologintext": "কোন পাতা সরিয়ে ফেলতে চাইলে আপনাকে অবশ্যই একজন নিবন্ধিত ব্যবহারকারী হতে হবে ও অ্যাকাউন্টে [[Special:UserLogin|প্রবেশ]] করতে হবে।",
        "pageinfo-article-id": "পাতার আইডি",
        "pageinfo-language": "পাতার তথ্যের ভাষা",
        "pageinfo-content-model": "পাতার বিষয়বস্তুর মডেল",
+       "pageinfo-content-model-change": "পরিবর্তন",
        "pageinfo-robot-policy": "রোবটের মাধ্যমে ইন্ডেক্স করা হচ্ছে",
        "pageinfo-robot-index": "অনুমোদিত",
        "pageinfo-robot-noindex": "অনুনমোদিন",
        "mediastatistics-header-office": "অফিস",
        "mediastatistics-header-archive": "সংকুচিত বিন্যাস",
        "mediastatistics-header-total": "সকল ফাইল",
+       "json-warn-trailing-comma": "JSON থেকে $1টি সর্বশেষ {{PLURAL:$1|কমা}} সরানো হয়েছে",
        "json-error-unknown": "JSON-এ একটি সমস্যা রয়েছে। ত্রুটি: $1",
+       "json-error-depth": "সর্বাধিক স্ট্যাকের গভীরতা অতিক্রম হয়েছে",
        "json-error-state-mismatch": "অকার্যকর বা ত্রুটিপূর্ণ JSON",
        "json-error-ctrl-char": "অক্ষর নিয়ন্ত্রণ ত্রুটি, সম্ভবত ভুল এনকোডকৃত",
        "json-error-syntax": "সিনট্যাক্স ত্রুটি",
        "log-action-filter-block": "বাধাদানের ধরন:",
        "log-action-filter-delete": "অপসারণের ধরন:",
        "log-action-filter-import": "আমদানির ধরন:",
+       "log-action-filter-managetags": "ট্যাগ ব্যবস্থাপনা কার্যের ধরন:",
+       "log-action-filter-move": "স্থানান্তরের ধরন:",
+       "log-action-filter-newusers": "অ্যাকাউন্ট তৈরির ধরন:",
        "log-action-filter-patrol": "টহলের ধরন:",
        "log-action-filter-protect": "সুরক্ষার ধরন:",
+       "log-action-filter-rights": "অধিকার পরিবর্তনের ধরন:",
        "log-action-filter-upload": "আপলোডের ধরন:",
        "log-action-filter-all": "সব",
        "log-action-filter-block-block": "বাধাদান",
        "log-action-filter-delete-revision": "সংশোধন অপসারণ",
        "log-action-filter-import-interwiki": "আন্তঃউইকি আমদানি",
        "log-action-filter-import-upload": "XML আপলোড কর্তৃক আমদানি",
+       "log-action-filter-managetags-create": "ট্যাগ সৃষ্টিকরণ",
+       "log-action-filter-managetags-delete": "ট্যাগ অপসারণ",
+       "log-action-filter-managetags-activate": "ট্যাগ সক্রিয়করণ",
+       "log-action-filter-managetags-deactivate": "ট্যাগ নিষ্ক্রিয়করণ",
        "log-action-filter-newusers-create": "বেনামী ব্যবহারকারী দ্বারা সৃষ্টি",
        "log-action-filter-newusers-create2": "নিবন্ধিত ব্যবহারকারী দ্বারা সৃষ্টি",
        "log-action-filter-newusers-autocreate": "স্বয়ংক্রিয় সৃষ্টি",
        "log-action-filter-upload-upload": "নতুন আপলোড",
        "log-action-filter-upload-overwrite": "পুনঃআপলোড",
        "authmanager-authn-no-primary": "সরবরাহকৃত পরিচয়পত্রের অনুমোদন যাচাই করা যায়নি।",
+       "authmanager-create-disabled": "অ্যাকাউন্ট সৃষ্টিকরণ নিষ্ক্রিয় করা হয়েছে।",
        "authmanager-create-from-login": "আপনার একাউন্ট তৈরি করতে, নীচের ক্ষেত্রগুলি পূরণ করুন।",
        "authmanager-authplugin-setpass-failed-title": "পাসওয়ার্ড পরিবর্তন ব্যর্থ হয়েছে",
        "authmanager-authplugin-setpass-bad-domain": "অবৈধ ডোমেইন।",
        "authprovider-confirmlink-success-line": "$1: সংযোগ করা সফল হয়েছে।",
        "authprovider-resetpass-skip-label": "উপেক্ষা করো",
        "authprovider-resetpass-skip-help": "পাসওয়ার্ড পুনঃস্থাপন করা উপেক্ষা করুন।",
+       "authform-newtoken": "টোকেন অনুপস্থিত। $1",
+       "authform-notoken": "টোকেন অনুপস্থিত",
        "authform-wrongtoken": "ভুল টোকেন",
        "specialpage-securitylevel-not-allowed-title": "অনুমতি নেই",
+       "specialpage-securitylevel-not-allowed": "দুঃখিত, আপনি এই পাতা ব্যবহার করতে অনুমতিপ্রাপ্ত নন কারণ আপনার পরিচয় যাচাই করা যায়নি।",
        "cannotauth-not-allowed-title": "অনুমতি অস্বীকৃত",
        "cannotauth-not-allowed": "আপনি এই পাতাটি ব্যবহার করতে অনুমতিপ্রাপ্ত নন।",
        "changecredentials": "পরিচয়পত্র পরিবর্তন করুন",
        "credentialsform-provider": "পরিচয়পত্রের ধরন:",
        "credentialsform-account": "অ্যাকাউন্টের নাম:",
        "linkaccounts": "অ্যাকাউন্ট সংযোগ করুন",
+       "linkaccounts-submit": "অ্যাকাউন্ট সংযুক্ত করুন",
+       "unlinkaccounts": "অ্যাকাউন্ট সংযোগ বিচ্ছিন্ন করুন",
+       "unlinkaccounts-success": "অ্যাকাউন্টের সংযোগ বিচ্ছিন্ন করা হয়েছে।",
        "userjsispublic": "অনুগ্রহ করে লক্ষ্য করুন: জাভাস্ক্রিপ্টের উপপাতাগুলিতে গোপনীয় তথ্য থাকা উচিত নয় যেহেতু অন্যান্য ব্যবহারকারীও এগুলি দেখতে পান।",
        "usercssispublic": "অনুগ্রহ করে লক্ষ্য করুন: সিএসএসের উপপাতাগুলিতে গোপনীয় তথ্য থাকা উচিত নয় যেহেতু অন্যান্য ব্যবহারকারীও এগুলি দেখতে পান।"
 }
index e631f1c..4538389 100644 (file)
        "yourpasswordagain": "Skrivit ho ker-tremen en-dro",
        "createacct-yourpasswordagain": "Kadarnaat ar ger-tremen",
        "createacct-yourpasswordagain-ph": "Skrivit ar ger-tremen adarre",
-       "remembermypassword": "Derc'hel soñj eus ma ger-tremen war an urzhiataer-mañ (evit $1 devezh{{PLURAL:$1||}} d'ar muiañ)",
        "userlogin-remembermypassword": "Derc'hel ac'hanon kevreet",
        "userlogin-signwithsecure": "Implijout ur gevreadenn suraet",
        "yourdomainname": "Ho tomani",
        "minoredit": "Kemm dister",
        "watchthis": "Evezhiañ ar pennad-mañ",
        "savearticle": "Enrollañ ar bajenn",
+       "savechanges": "Enrollañ ar c'hemmoù",
+       "publishpage": "Embann ar bajenn",
+       "publishchanges": "Embann ar c'hemmoù",
        "preview": "Rakwelet",
        "showpreview": "Rakwelet",
        "showdiff": "Diskouez ar c'hemmoù",
index ee07df4..503f347 100644 (file)
        "yourpasswordagain": "Ponovo upišite lozinku:",
        "createacct-yourpasswordagain": "Potvrdite lozinku",
        "createacct-yourpasswordagain-ph": "Unesite lozinku opet",
-       "remembermypassword": "Zapamti moju lozinku na ovom pregledniku (najduže $1 {{PLURAL:$1|dan|dana}})",
        "userlogin-remembermypassword": "Ostavi me prijavljenog/-u",
        "userlogin-signwithsecure": "Koristite sigurnu konekciju",
        "yourdomainname": "Vaš domen:",
        "minoredit": "Ovo je manja izmjena",
        "watchthis": "Prati ovu stranicu",
        "savearticle": "Sačuvaj stranicu",
+       "savechanges": "Sačuvaj izmjene",
        "publishpage": "Objavi stranicu",
        "publishchanges": "Objavi izmjene",
        "preview": "Pregled stranice",
index 9cafa75..9e9e6b6 100644 (file)
        "yourpasswordagain": "Escriviu una altra vegada la contrasenya",
        "createacct-yourpasswordagain": "Confirmeu la contrasenya",
        "createacct-yourpasswordagain-ph": "Introduïu de nou la contrasenya",
-       "remembermypassword": "Recorda la contrasenya entre sessions (per un màxim de $1 {{PLURAL:$1|dia|dies}})",
        "userlogin-remembermypassword": "Mantén-me connectat",
        "userlogin-signwithsecure": "Connexió segura",
        "cannotloginnow-title": "Ara no es pot iniciar la sessió",
        "content-model-json": "JSON",
        "content-json-empty-object": "Objecte buit",
        "content-json-empty-array": "Matriu buida",
+       "deprecated-self-close-category": "Pàgines que usen etiquetes HTML autotancades no vàlides",
        "duplicate-args-warning": "<strong>Avís:</strong> [[:$1]] crida [[:$2]] amb més d'un valor pel paràmetre «$3». Només s'utilitzarà el darrer valor proporcionat.",
        "duplicate-args-category": "Pàgines amb arguments duplicats en utilització de plantilles",
        "duplicate-args-category-desc": "La pàgina conté crides a plantilles que fan servir duplicats d'arguments, com ara <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> o <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
index ee26071..088c746 100644 (file)
        "yourpasswordagain": "Юха язъе пароль:",
        "createacct-yourpasswordagain": "Бакъе пароль",
        "createacct-yourpasswordagain-ph": "Кхин цкъа язъе пароль",
-       "remembermypassword": "Даглаца сан дӀаяздар хӀокху компьютеран тӀехь (цхьан $1 {{PLURAL:$1|дийнахь}})",
        "userlogin-remembermypassword": "Системин чохь Ӏойла",
        "userlogin-signwithsecure": "Ларийна цхьаьнакхетар",
        "cannotloginnow-title": "ХӀинца чудаха таро яц",
        "minoredit": "Жима хийцам",
        "watchthis": "ХӀара агӀо тергаме могӀанан юкъатоха",
        "savearticle": "АгӀо дӀаязъян",
+       "savechanges": "Ӏалашбе хийцамаш",
+       "publishpage": "АгӀо кхолла",
        "preview": "Хьалххе хьажар",
        "showpreview": "Хьалха хьажар",
        "showdiff": "Бина болу хийцамашка хьажар",
        "searchprofile-articles-tooltip": "$1 чохь лахар",
        "searchprofile-images-tooltip": "Файлаш лахар",
        "searchprofile-everything-tooltip": "Массо агӀонашкахь лахар (дийцаре агӀонашца)",
-       "searchprofile-advanced-tooltip": "Ð\94еÑ\85аÑ\80Ñ\86а Ð¹Ð¾Ð»Ñ\83 Ñ\86Ó\80еÑ\80ийн Ð°Ð½ашкахь лахар",
+       "searchprofile-advanced-tooltip": "Ð\94еÑ\85аÑ\80Ñ\86а Ð¹Ð¾Ð»Ñ\83 Ñ\86Ó\80еÑ\80ийн Ð¼ÐµÑ\82Ñ\82игашкахь лахар",
        "search-result-size": "$1 ({{PLURAL:$2|$2 дош|$2 дешнаш}})",
        "search-result-category-size": "$1 {{PLURAL:$1|юкъаяр}} ($2 {{PLURAL:$2|1=бухара категори|бухара категореш}}, $3 {{PLURAL:$3|1=файл|файлаш}}).",
        "search-redirect": "(дӀасахьажийна $1)",
        "search-showingresults": "{{PLURAL:$4|Карийна <strong>$1</strong> — цхьаъ агӀо|И дош карийна <strong>$3</strong> агӀонашкахь, царех гойту $2 агӀо}}",
        "search-nonefound": "Дехаре терра цхьа хӀума ца карийна.",
        "powersearch-legend": "Шуьйра лахар",
-       "powersearch-ns": "ЦÓ\80еÑ\80ийн Ð°Ð½ашкахь лахар:",
+       "powersearch-ns": "ЦÓ\80еÑ\80ийн Ð¼ÐµÑ\82Ñ\82игашкахь лахар:",
        "powersearch-togglelabel": "Билгалдан:",
        "powersearch-toggleall": "Массо",
        "powersearch-togglenone": "ХӀумма цаоьшу",
        "tooltip-ca-nstab-category": "Категорешан агӀо",
        "tooltip-minoredit": "Къастам бé хӀокху хийцамна кӀеззиг болуш санна",
        "tooltip-save": "Хьан хийцамаш Ӏалашбой",
+       "tooltip-publish": "Гучубаха хьой бина хийцамаш",
        "tooltip-preview": "Дехар до, агӀо Ӏалаш ярал хьалха хьажа муха ю из!",
        "tooltip-diff": "Гайта долуш долу йозанах бина болу хийцам.",
        "tooltip-compareselectedversions": "ХӀокху агӀона шина хаьржина версийн башхалле хьажар.",
index 1fb2401..c6b47a7 100644 (file)
        "yourpasswordagain": "دیسان تێپەڕوشەکە بنووسەوە:",
        "createacct-yourpasswordagain": "تێپەروشە پشتڕاست بکەرەوە",
        "createacct-yourpasswordagain-ph": "تێپەروشە دیسان بنووسەوە",
-       "remembermypassword": "چوونە ژوورەوەم لەسەر ئەم کۆمپیوتەرە پاشەکەوت بکە (ئەو پەڕی $1 {{PLURAL:$1|ڕۆژ}}ە)",
        "userlogin-remembermypassword": "چوونەژوورەوەکەم ڕابگرە",
        "userlogin-signwithsecure": "پەیوەندیی دڵنیا بەکاربھێنە",
        "yourdomainname": "دۆمەینەکەت:",
        "minoredit": "ئەمە دەستکارییەکی بچووکە",
        "watchthis": "ئەم پەڕەیە بخە ژێر چاودێری",
        "savearticle": "پەڕەکە پاشەکەوت بکە",
+       "savechanges": "پاشەکەوتکردنی گۆڕانکارییەکان",
        "preview": "پێشبینین",
        "showpreview": "پێشبینین نیشان بدە",
        "showdiff": "گۆڕانکارییەکان نیشان بدە",
index a2a081a..3bab099 100644 (file)
        "yourpasswordagain": "Zopakujte heslo:",
        "createacct-yourpasswordagain": "Potvrzení hesla",
        "createacct-yourpasswordagain-ph": "Zadejte heslo ještě jednou",
-       "remembermypassword": "Zapamatovat si mé přihlášení na tomto počítači (maximálně $1 {{PLURAL:$1|den|dny|dní}})",
        "userlogin-remembermypassword": "Přihlásit trvale",
        "userlogin-signwithsecure": "Používat zabezpečené připojení",
        "cannotloginnow-title": "Momentálně se nelze přihlásit",
        "file-thumbnail-no": "Jméno souboru začíná na <strong>$1</strong>.\nMožná to je obrázek ve zmenšené velikosti ''(náhled)''.\nNačtěte soubor v plném rozlišením, pokud je k dispozici, nebo změňte jméno souboru.",
        "fileexists-forbidden": "Soubor s tímto názvem již existuje a není dovoleno ho přepsat.\nPokud chcete přesto soubor načíst, vraťte se a zvolte jiný název.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Soubor s tímto názvem již existuje ve sdíleném úložišti. Pokud přesto chcete váš soubor načíst, vraťte se a zvolte jiný název. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Načítaný soubor je přesným duplikátem aktuální revize souboru <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Načítaný soubor je přesným duplikátem {{PLURAL:$2|starší revize|starších revizí}} souboru <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Tento soubor je duplikát {{PLURAL:$1|následujícího souboru|následujících souborů}}:",
        "file-deleted-duplicate": "Identický soubor k tomuto ([[:$1]]) byl již dříve smazán. Před tím, než soubor znovu nahrajete, byste měli zkontrolovat záznamy o předchozím smazání.",
        "file-deleted-duplicate-notitle": "Identický soubor k tomuto byl již dříve smazán a název byl utajen.\nPřed tím, než soubor znovu nahrajete, byste měli požádat někoho, kdo může prohlížet utajené soubory, aby situaci zkontroloval.",
        "filerevert-submit": "Vrátit zpět",
        "filerevert-success": "Soubor '''[[Media:$1|$1]]''' byl vrácen zpět na [$4 verzi z $3 $2].",
        "filerevert-badversion": "Není dostupná předchozí verze tohoto souboru s odpovídající časovou značkou.",
+       "filerevert-identical": "Aktuální verze souboru se již od vybrané verze neliší.",
        "filedelete": "Smazání souboru $1",
        "filedelete-legend": "Smazat soubor",
        "filedelete-intro": "Chystáte se smazat soubor '''[[Media:$1|$1]]''' i s celou historií.",
        "changecontentmodel-nodirectediting": "Model obsahu $1 nepodporuje přímou editaci",
        "changecontentmodel-emptymodels-title": "Nejsou k dispozici žádné modely obsahu",
        "changecontentmodel-emptymodels-text": "Obsah stránky [[:$1]] nelze zkonvertovat na žádný typ.",
-       "log-name-contentmodel": "Kniha změny modelů obsahu",
+       "log-name-contentmodel": "Kniha změn modelů obsahu",
        "log-description-contentmodel": "Události týkající se modelů obsahu stránek",
        "logentry-contentmodel-new": "$1 {{GENDER:$2|založil|založila}} stránku $3 za použití nestandardního modelu obsahu „$5“",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|změnil|změnila}} model obsahu stránky $3 z „$4“ na „$5“",
        "pageinfo-article-id": "ID stránky",
        "pageinfo-language": "Jazyk obsahu stránky",
        "pageinfo-content-model": "Model obsahu stránky",
+       "pageinfo-content-model-change": "změnit",
        "pageinfo-robot-policy": "Indexování roboty",
        "pageinfo-robot-index": "Dovoleno",
        "pageinfo-robot-noindex": "Zakázáno",
index 4fb3f7b..c6bd656 100644 (file)
        "yourpasswordagain": "Gentag adgangskode",
        "createacct-yourpasswordagain": "Bekræft adgangskode",
        "createacct-yourpasswordagain-ph": "Indtast adgangskode igen",
-       "remembermypassword": "Husk mit brugernavn i denne browser (højst $1 {{PLURAL:$1|dag|dage}})",
        "userlogin-remembermypassword": "Husk mig",
        "userlogin-signwithsecure": "Brug sikker forbindelse",
        "cannotloginnow-title": "Kan ikke logge ind på nuværende tidspunkt",
        "minoredit": "Dette er en mindre ændring",
        "watchthis": "Overvåg denne side",
        "savearticle": "Gem side",
+       "savechanges": "Gem ændringer",
        "publishpage": "Offentliggør side",
        "publishchanges": "Offentliggør ændringer",
        "preview": "Forhåndsvisning",
        "rightslogtext": "Dette er en log over ændringer i brugeres rettigheder.",
        "action-read": "se denne side",
        "action-edit": "redigere denne side",
-       "action-createpage": "oprette sider",
-       "action-createtalk": "oprette diskussionssider",
+       "action-createpage": "oprette denne side",
+       "action-createtalk": "oprette denne diskussionsside",
        "action-createaccount": "Oprette denne brugerkonto",
        "action-history": "se historik for denne side",
        "action-minoredit": "markere denne redigering som mindre",
        "upload-form-label-infoform-title": "Detaljer",
        "upload-form-label-infoform-name": "Navn",
        "upload-form-label-infoform-description": "Beskrivelse",
+       "upload-form-label-usage-title": "Anvendelse",
        "upload-form-label-usage-filename": "Filnavn",
        "upload-form-label-infoform-categories": "Kategorier",
        "upload-form-label-infoform-date": "Dato",
        "log-title-wildcard": "Søg i titler som begynder med teksten",
        "showhideselectedlogentries": "Vis/skjul de markerede loghændelser",
        "log-edit-tags": "Rediger tags i valgte logposter",
+       "checkbox-all": "Alle",
+       "checkbox-none": "Ingen",
        "allpages": "Alle sider",
        "nextpage": "Næste side ($1)",
        "prevpage": "Forrige side ($1)",
        "listgrouprights-namespaceprotection-header": "Navnerumsbegrænsninger",
        "listgrouprights-namespaceprotection-namespace": "Navnerum",
        "listgrouprights-namespaceprotection-restrictedto": "Rettighed(er) der giver brugeren mulighed for at redigere",
+       "listgrants-rights": "Rettigheder",
        "trackingcategories": "Sporingskategorier",
        "trackingcategories-summary": "Denne side viser sporing af de kategorier, som er automatisk udfyldt af MediaWiki-softwaren. Deres navne kan ændres ved at ændre de relevante system-beskeder i {{ns:8}}-navnerummet.",
        "trackingcategories-msg": "Sporingskategori",
index 2f0ea5e..8f38fa2 100644 (file)
        "yourpasswordagain": "Passwort wiederholen:",
        "createacct-yourpasswordagain": "Passwort bestätigen",
        "createacct-yourpasswordagain-ph": "Gib das Passwort erneut ein",
-       "remembermypassword": "Mit diesem Browser dauerhaft angemeldet bleiben (maximal $1 {{PLURAL:$1|Tag|Tage}})",
        "userlogin-remembermypassword": "Angemeldet bleiben",
        "userlogin-signwithsecure": "Sichere Verbindung verwenden",
+       "cannotlogin-title": "Die Anmeldung ist nicht möglich.",
+       "cannotlogin-text": "Die Anmeldung ist nicht möglich.",
        "cannotloginnow-title": "Anmeldung nicht erfolgreich",
        "cannotloginnow-text": "Eine Anmeldung ist mit Verwendung von $1 nicht möglich.",
+       "cannotcreateaccount-title": "Die Erstellung von Benutzerkonten ist nicht möglich.",
+       "cannotcreateaccount-text": "Die direkte Erstellung von Benutzerkonten ist auf diesem Wiki nicht aktiviert.",
        "yourdomainname": "Deine Domain:",
        "password-change-forbidden": "Du kannst auf diesem Wiki keine Passwörter ändern.",
        "externaldberror": "Entweder liegt ein Fehler bei der externen Authentifizierung vor oder du darfst dein externes Benutzerkonto nicht aktualisieren.",
        "nextn-title": "{{PLURAL:$1|Folgendes Ergebnis|Folgende $1 Ergebnisse}}",
        "shown-title": "Zeige $1 {{PLURAL:$1|Ergebnis|Ergebnisse}} pro Seite",
        "viewprevnext": "Zeige ($1 {{int:pipe-separator}} $2) ($3)",
-       "searchmenu-exists": "<strong>Es gibt eine Seite, die den Namen „[[:$1]]“ hat.</strong> {{PLURAL:$2|0=|Weitere Suchergebnisse anzeigen.}}",
+       "searchmenu-exists": "<strong>Es gibt eine Seite, die den Namen „[[:$1]]“ hat.</strong> {{PLURAL:$2|0=|Weitere Suchergebnisse:}}",
        "searchmenu-new": "<strong>Erstelle die Seite „[[:$1]]“ in diesem Wiki.</strong> {{PLURAL:$2|0=|Siehe auch die über deine Suche gefundene Seite.|Siehe auch die gefundenen Suchergebnisse.}}",
        "searchprofile-articles": "Inhaltsseiten",
        "searchprofile-images": "Multimedia",
        "file-thumbnail-no": "Der Dateiname beginnt mit <strong>$1</strong>. Dies deutet auf ein Bild verringerter Größe ''(Minitatur)'' hin.\nBitte prüfe, ob du das Bild in voller Auflösung vorliegen hast und lade dieses unter dem Originalnamen hoch.",
        "fileexists-forbidden": "Unter diesem Namen existiert bereits eine Datei und sie kann nicht überschrieben werden. Bitte gehe zurück und lade die Datei unter einem anderen Namen hoch. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Unter diesem Namen existiert bereits eine Datei im zentralen Medienarchiv.\nWenn du diese Datei trotzdem hochladen möchtest, gehe bitte zurück und ändere den Namen.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Die hochgeladene Datei ist ein exaktes Duplikat der aktuellen Version von <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Die hochgeladene Datei ist ein exaktes Duplikat {{PLURAL:$2|einer älteren Version|von älteren Versionen}} von <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Diese Datei ist ein Duplikat der folgenden {{PLURAL:$1|Datei|$1 Dateien}}:",
        "file-deleted-duplicate": "Eine mit dieser identische Datei ([[:$1]]) wurde früher gelöscht. Sieh das Lösch-Logbuch ein, bevor du sie hochlädst.",
        "file-deleted-duplicate-notitle": "Eine identische Datei wurde kürzlich gelöscht und der Titel wurde unterdrückt.\nDu solltest jemanden fragen, der die Möglichkeit hat, die unterdrückten Dateidaten anzusehen, um die Situation vor dem erneuten Hochladen zu überprüfen.",
        "filerevert-submit": "Zurücksetzen",
        "filerevert-success": "'''[[Media:$1|$1]]''' wurde auf die [$4 Version vom $2, $3 Uhr] zurückgesetzt.",
        "filerevert-badversion": "Es gibt keine Version der Datei zu dem angegebenen Zeitpunkt.",
+       "filerevert-identical": "Die aktuelle Version der Datei ist bereits mit der ausgewählten Version identisch.",
        "filedelete": "Lösche „$1“",
        "filedelete-legend": "Lösche Datei",
        "filedelete-intro": "Du löschst die Datei '''„[[Media:$1|$1]]“''' inklusive ihrer Versionsgeschichte.",
        "pageinfo-article-id": "Seitenkennnummer",
        "pageinfo-language": "Seiteninhaltssprache",
        "pageinfo-content-model": "Seiteninhaltsmodell",
+       "pageinfo-content-model-change": "ändern",
        "pageinfo-robot-policy": "Indizierung durch Suchmaschinen",
        "pageinfo-robot-index": "Erlaubt",
        "pageinfo-robot-noindex": "Nicht erlaubt",
index 547d300..a179a77 100644 (file)
@@ -76,7 +76,7 @@
        "editfont-monospace": "Terzê nusteyê sabıtcagırewtoği",
        "editfont-sansserif": "Fontê Sans-serifi",
        "editfont-serif": "Font (çêşıdê nuştey) Serif",
-       "sunday": "Kırê (Bazar)",
+       "sunday": "Kırê",
        "monday": "Dışeme",
        "tuesday": "Sêşeme",
        "wednesday": "Çarşeme",
        "oct": "Tşv",
        "nov": "Tşp",
        "dec": "Kan",
-       "january-date": "Çele  $1",
-       "february-date": "Sıbate $1",
-       "march-date": "Adar $1",
-       "april-date": "Nisane $1",
-       "may-date": "Gulane $1",
-       "june-date": "{{PLURAL:$1|1=1ᵉ|$1}} Heziran",
-       "july-date": "Temuz $1",
-       "august-date": "Tebaxe $1",
-       "september-date": "Keşkelun $1",
-       "october-date": "Tışrino Verên $1",
-       "november-date": "Tışrino Peyên $1",
-       "december-date": "Kanun $1",
+       "january-date": "$1 Çele",
+       "february-date": "$1 Sıbate",
+       "march-date": "$1 Adar",
+       "april-date": "$1 Nisane",
+       "may-date": "$1 Gulane",
+       "june-date": "$1 Heziran",
+       "july-date": "$1 Temuze",
+       "august-date": "$1 Tebaxe",
+       "september-date": "$1 Keşkelun",
+       "october-date": "$1 Tışrino Verên",
+       "november-date": "$1 Tışrino Peyên",
+       "december-date": "$1 Kanun",
        "period-am": "AM",
        "period-pm": "PM",
        "pagecategories": "{{PLURAL:$1|Kategori|Kategoriy}}",
        "category-empty": "''Ena kategoriye de hewna qet nuştey ya zi medya çıniyê.''",
        "hidden-categories": "{{PLURAL:$1|Kategoriya nımıtiye|Kategoriyê nımıtey}}",
        "hidden-category-category": "Kategoriyê nımıtey",
-       "category-subcat-count": "{{PLURAL:$2|Na kategoriya de $1 bınkategoriyay estê.|$2 kategoriyan ra $1 bınkategoriyay asenê.}} \n(K) Kategori (D) Dosya (P) Pela",
-       "category-subcat-count-limited": "Na kategoriya de {{PLURAL:$1|ena kategoriya bınên est a|enê $1 kategoriyay bınêni est ê}}.",
+       "category-subcat-count": "{{PLURAL:$2|Na kategoriye de $1 bınkategoriy estê.|$2 kategoriyan ra $1 kategoriyê bınêni asenê.}} \n{| border=\"1\" cellpadding=\"2\" cellspacing=\"0\" align=\"left\" style=\"margin-left:1em; background:peru; border: 1px #aaa solid; border-collapse: collapse; font-size: 95%;\"\n| align=\"center\" |(K) Kategoriye (D) Dosya (P) Peli (M)  Medya\n|}",
+       "category-subcat-count-limited": "Na kategoriye de {{PLURAL:$1|na kategoriya bınêne esta|nê $1 kategoriyê bınêni estê}}.",
        "category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Ebe $2 ra pêro piya {{PLURAL:$1|ena pela na kategoriye dera|$1 enê peli na kategoriye derê.}}}}",
        "category-article-count-limited": "{{PLURAL:$1|Pela cêrêne|$1 Pelê cêrêni}} na kategoriye derê.",
-       "category-file-count": "<noinclude>{{PLURAL:$2|Na kategoriya tenya dosyanê cêrênan muhtewa kena.}}</noinclude>\n*Na kategoriya de $2 dosyan ra {{PLURAL:$1|yew dosya tenêka esta| $1 dosyay asenê}}.",
+       "category-file-count": "{{PLURAL:$2|Na kategori tenya dosya ya cêri muhtewa kena.|Na kategori de $2 ra pêro piya {{PLURAL:$1|1 dosya est a|$1 dosyey est ê}}.}}",
        "category-file-count-limited": "{{PLURAL:$1|Dosya cêrêne|$1 Dosyê cêrêni}} na kategoriye derê.",
        "listingcontinuesabbrev": "dewam...",
        "index-category": "Pelê endeksıni",
        "specialpage": "Pela xısusiye",
        "personaltools": "Hacetê şexsiy",
        "articlepage": "Pera zerreki bıvin",
-       "talk": "Werênayış",
+       "talk": "Vaten",
        "views": "Asayışi",
        "toolbox": "Haceti",
        "userpage": "Pela karberi bıvêne",
        "toc": "Sernameyê meselan",
        "showtoc": "bıasene",
        "hidetoc": "bınımne",
-       "collapsible-collapse": "Teng ke",
+       "collapsible-collapse": "Teng kı",
        "collapsible-expand": "Hera ke",
-       "confirmable-confirm": "{{GENDER:$1|Şıma }} do emeli?",
+       "confirmable-confirm": "{{GENDER:$1|Şıma}} pêbawerê?",
        "confirmable-yes": "Eya",
        "confirmable-no": "Nê",
        "thisisdeleted": "Bıvêne ya zi $1 peyser biya?",
        "cannotlogoutnow-title": "Enewke ronıştışo nêracneyêno",
        "welcomeuser": "Ğeyr amey, $1!",
        "welcomecreation-msg": "Hesabê şıma abiyo.\n[[Special:Preferences|{{SITENAME}} vurnayişê tercihanê xo]], xo vir ra mekere.",
-       "yourname": "Nameyê karberi:",
-       "userlogin-yourname": "Nameyê karberi",
+       "yourname": "Namey karberi:",
+       "userlogin-yourname": "Namey karberi",
        "userlogin-yourname-ph": "Nameyê xoyê karberi cı kewe",
        "createacct-another-username-ph": "Nameyê karberi cı kewe",
        "yourpassword": "Parola",
        "yourpasswordagain": "Parola reyna bınusne:",
        "createacct-yourpasswordagain": "Parola tesdiq ke",
        "createacct-yourpasswordagain-ph": "Parola fına cıkewe",
-       "remembermypassword": "Parola mı nê cıgeyrayoği de biya xo viri (seba tewr zêde $1 {{PLURAL:$1|roce|rocan}})",
        "userlogin-remembermypassword": "Mı biya xo viri",
        "userlogin-signwithsecure": "Ebe teqdimkerê asayişın cıkewe",
        "cannotloginnow-title": "Enewke ronıştışo nêabeno",
        "cannotloginnow-text": "$1 karkerdışa ronıştış akerdış mıkum niyo.",
-       "yourdomainname": "Nameyê şıma yo meydani",
+       "yourdomainname": "Yewdestê şıma:",
        "password-change-forbidden": "Şıma na wiki de nêşenê parola bıvurnê.",
        "externaldberror": "Ya database de xeta esta ya zi heqê şıma çino şıma no hesab bıvurni.",
        "login": "Cı kewe",
        "mergehistory-box": "revizyonê pelanî yew bike:",
        "mergehistory-from": "Pela çımey:",
        "mergehistory-into": "Pela destinasyonî",
-       "mergehistory-list": "tarixê vurnayîşî ke eşkeno yew bi.",
+       "mergehistory-list": "Tarixê vurnayışiyo yewbiyaye",
        "mergehistory-merge": "[[:$1]] qey ney revizyonê cêrini [[:$2]] şıma ekeni piyawani. Benatê wexto muwaqqet de piyayanayişê rezizyonan de tuşa radyo bıxebitne.",
        "mergehistory-go": "Yew bıyaye vurriyayışa bıasne",
        "mergehistory-submit": "revizyonî yew bike",
        "mergelog": "Qeydé zew kerdışi",
        "revertmerge": "Abırnê",
        "mergelogpagetext": "Cêr de yew liste esta ke mocnena ra, raya tewr peyêne kamci pela tarixi be a bine ra şanawa pê.",
-       "history-title": "Tarixê çım ra viyarnayışë \"$1\"",
+       "history-title": "Tarixê çımraviyarnayışê \"$1\"",
        "difference-title": "Pela \"$1\" ferqê çım ra viyarnayışan",
        "difference-title-multipage": "Ferkê pelan dê \"$1\" u \"$2\"",
        "difference-multipage": "(Ferqê pelan)",
        "action-managechangetags": "Vıraz u etiketa aktiv (me) ke",
        "action-applychangetags": "Vurnayışana piya etiket kerdışi zi dezge fi",
        "action-purge": "Ane perer newe ke",
-       "nchanges": "$1 {{PLURAL:$1|fın vurna|fıni vurna}}",
-       "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ra yok wazino}}",
+       "nchanges": "$1 {{PLURAL:$1|vurnayış|vurnayışi}}",
+       "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ziyaretê peyêni ra nata}}",
        "enhancedrc-history": "tarix",
        "recentchanges": "Vurriyayışê peyêni",
        "recentchanges-legend": "Tercihê vurnayışanê peyênan",
        "protectedpages-unknown-performer": "Karbero nêzanaye",
        "protectedtitles": "Sernameyê pawıteyi",
        "protectedtitlesempty": "pê ney parametreyan sernuşteyê pawite çinê",
-       "protectedtitles-submit": "Sernaman bımocne",
+       "protectedtitles-submit": "Sernameyan bımocne",
        "listusers": "Listeyê Karberan",
        "listusers-editsonly": "Teyna karberan bimucne ke ey nuştê",
        "listusers-creationsort": "goreyê wextê vıraştışi rêz ker",
        "emailsenttext": "e-mailê şıma erşawiya/ruşiya",
        "emailuserfooter": "na e-posta hetê ıney ra $1 erşawiya $2 no/na karberi/e re. pê fonksiyonê \"Karberi/e re e-posta bıerşaw\" no {{SITENAME}} keyepeli erşawiya.",
        "usermessage-summary": "Mesacê sistemi caverde.",
-       "usermessage-editor": "Mesaj berdoxe sistemi",
+       "usermessage-editor": "Xeberdarê sistemi",
        "usermessage-template": "MediaWiki:UserMessage",
        "watchlist": "Lista seyrkerdışi",
        "mywatchlist": "Lista seyrkerdışi",
        "cant-move-user-page": "desturê şıma çino, şıma pelanê karberani bıkırışi (bê pelê cerıni).",
        "cant-move-to-user-page": "desturê şıma çino, şıma yew peli bıkırışi pelê yew karberi.",
        "newtitle": "Sernameyo newe:",
-       "move-watch": "Peler seyr ke",
+       "move-watch": "Na pele seyr ke",
        "movepagebtn": "Pele bere",
        "pagemovedsub": "Berdışi kerd temam",
        "movepage-moved": "'''\"$1\" berd \"$2\"'''",
        "tooltip-n-mainpage-description": "Şo pela seri",
        "tooltip-n-portal": "Heqa proceyi de, çı şenay bıkerê, çı koti vêniyeno",
        "tooltip-n-currentevents": "Vurnayışanê peyênan de melumatê pey bıvêne",
-       "tooltip-n-recentchanges": "Wiki de lista vurnayışanê peyênan",
+       "tooltip-n-recentchanges": "Wiki de yew lista vurriyayışanê peyênan",
        "tooltip-n-randompage": "Pelê da raştameyiye bar ke",
        "tooltip-n-help": "Cayê peştigırewtışi",
        "tooltip-t-whatlinkshere": "Lista pelanê wikiya pêroina ke tiya gırê bena",
        "tooltip-ca-nstab-main": "Pela zerreki bıvêne",
        "tooltip-ca-nstab-user": "Pela karberi bıvêne",
        "tooltip-ca-nstab-media": "Pela medya bıvêne",
-       "tooltip-ca-nstab-special": "Na pelaya xas a, şıma nêşenê sero vurnayış bıkerê",
+       "tooltip-ca-nstab-special": "Na yew pela xasa, şıma nêşenê sero vurnayış bıkerê",
        "tooltip-ca-nstab-project": "Pela proceyi bıvêne",
        "tooltip-ca-nstab-image": "Pera dosyayer bıvin",
        "tooltip-ca-nstab-mediawiki": "Mesacê sistemi bımocne",
        "pageinfo-header-edits": "Veréna timar kerdışi",
        "pageinfo-header-restrictions": "Sıtarkerdışê pele",
        "pageinfo-header-properties": "Xısusiyetê pele",
-       "pageinfo-display-title": "Sernuştey bımocne",
+       "pageinfo-display-title": "Sernuşteyo ke mosneyêno",
        "pageinfo-default-sort": "Hesıbyaye mırfeyo kılm",
        "pageinfo-length": "Derdeya pela (bayti heta)",
        "pageinfo-article-id": "Kamiya pele",
        "exif-pixelydimension": "Berzeya resimi",
        "exif-usercomment": "Mışewreyê karberi",
        "exif-relatedsoundfile": "Derhekê dosya yê vengi",
-       "exif-datetimeoriginal": "Zeman u tarixê data varaziyayişi",
-       "exif-datetimedigitized": "Zeman u tarixê dicital kerdişi",
+       "exif-datetimeoriginal": "Demê afernayışê dayeyo sıfteyıni",
+       "exif-datetimedigitized": "Zeman û tarixê dicitalkerdışi",
        "exif-subsectime": "ZemanTarix saniyeyibini",
        "exif-subsectimeoriginal": "ZemanTarixOricinal saniyeyibini",
        "exif-subsectimedigitized": "ZemanTarixDicital saniyeyibini",
        "version": "Versiyon",
        "version-extensions": "Ekstensiyonî ke ronaye",
        "version-skins": "Bar kerde bejni",
-       "version-specialpages": "Pelanê xasiyan",
+       "version-specialpages": "Pelê xısusiyi",
        "version-parserhooks": "Çengelê Parserî",
        "version-variables": "Vurnayeyî",
        "version-antispam": "Spam vındarnayış",
        "specialpages-note-top": "Kıtabek",
        "specialpages-note": "* Pelê xasê normali.\n* <span class=\"mw-specialpagerestricted\">Pelê xasê nımıtey.</span>",
        "specialpages-group-maintenance": "Raporê pawıtışi",
-       "specialpages-group-other": "Pelê xasiyê bini",
+       "specialpages-group-other": "Pelê xısusiyê bini",
        "specialpages-group-login": "Dekew / hesab vıraz",
        "specialpages-group-changes": "Vurnayışê peyêni û qeydi",
        "specialpages-group-media": "Raporê medya û barkerdışi",
        "tag-filter-submit": "Avrêc",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiket|Etiketi}}]]: $2)",
        "tags-title": "Etiketan",
-       "tags-intro": "Eno pel de listeyê eyiketî este ke belki software pê ey edit kenî.",
+       "tags-intro": "Ena per şena ju vurnayışa etiketinu maney nustekeni liste  kena",
        "tags-tag": "Nameyê etiketi",
        "tags-display-header": "Listeyê vurnayîşî de esayîş",
        "tags-description-header": "Tam arezekerdışê maneyê cı",
        "tags-actions-header": "Kerdışi",
        "tags-active-yes": "Eya",
        "tags-active-no": "Nê",
-       "tags-source-extension": "Teref dê yo dergeneki ra şınasiyayo",
+       "tags-source-extension": "Kışta ju dergeneki ra şınasêna",
        "tags-edit": "bıvurne",
        "tags-delete": "bestere",
        "tags-activate": "Aktiv ke",
index 08857b4..f972918 100644 (file)
@@ -43,6 +43,7 @@
        "tog-watchlisthidebots": "बोट सम्पादनहरू ध्यान सूचीबठेई लुकाउन्या",
        "tog-watchlisthideminor": "मेरा सम्पादनहरू ध्यान सूचीबाट लुकाउन्या",
        "tog-watchlisthideliu": "प्रवेश गरेका प्रयोगकर्ताहरूको सम्पादन ध्यान सूचीबठेई लुकाउन्या",
+       "tog-watchlistreloadautomatically": "जज्ज्याँलै फिल्टर बदेलिन्छ इच्छासूची आफुइ रिलोड अर: (जावास्क्रिप्ट चायीन्छ)",
        "tog-watchlisthideanons": "अज्ञात प्रयोगकर्ताहरूबाट गरिएको सम्पादन ध्यान सूचीबठेई लुकाउन्या",
        "tog-watchlisthidepatrolled": "बोट सम्पादनहरू ध्यान सूचीबठेई लुकाउन्या",
        "tog-watchlisthidecategorization": "पृष्ठहरूको श्रेणीकरण लुकौन्या",
        "editfont-serif": "सेरिफ फन्ट",
        "sunday": "आइतबार",
        "monday": "सौउबार",
-       "tuesday": "माà¤\82à¤\97लबार",
+       "tuesday": "माà¤\99लबार",
        "wednesday": "बुधबार",
-       "thursday": "बिपैबार",
+       "thursday": "बà¥\80पैबार",
        "friday": "शुकबार",
        "saturday": "छन्चरबार",
        "sun": "आइत",
        "mon": "सौउ",
        "tue": "मांगल",
-       "wed": "बà¥\81ध",
-       "thu": "बिपै",
+       "wed": "बà¥\8bऽ",
+       "thu": "बà¥\80पै",
        "fri": "शुक",
        "sat": "छन्चर",
        "january": "जनवरी",
        "october-gen": "अक्टोबर",
        "november-gen": "नोभेम्बर",
        "december-gen": "डिसेम्बर",
-       "jan": "जनवरी",
+       "jan": "जन",
        "feb": "फेब्रुअरी",
        "mar": "मार्च",
-       "apr": "अप्रि",
+       "apr": "अप्रि",
        "may": "मे",
        "jun": "जुन",
-       "jul": "जुलाई",
-       "aug": "अगस्ट",
-       "sep": "सेप्टेम्बर",
-       "oct": "अक्टोबर",
-       "nov": "नोभेम्बर",
-       "dec": "डिसेम्बर",
+       "jul": "जुल",
+       "aug": "अग",
+       "sep": "सेप्ट",
+       "oct": "अक्ट",
+       "nov": "नोभ",
+       "dec": "डिस",
        "january-date": "जनवरी $1",
        "february-date": "फेब्रुअरी $1",
        "march-date": "मार्च $1",
        "december-date": "डिसेम्बर $1",
        "period-am": "रात १२ बज्या बठे छाकला सम्म",
        "period-pm": "छाकला बठे रात १२ बज्या सम्म",
-       "pagecategories": "{{PLURAL:$1|शà¥\8dरà¥\87णà¥\80|शà¥\8dरà¥\87णà¥\80हरà¥\82}}",
-       "category_header": "\"$1\" श्रेणीमी भया लेखहरू",
+       "pagecategories": "{{PLURAL:$1|शà¥\8dरà¥\87णà¥\80|शà¥\8dरà¥\87णà¥\80न}}",
+       "category_header": "\"$1\" श्रेणीमी भयाऽ लेखअन",
        "subcategories": "उपश्रेणीहरू",
        "category-media-header": "\"$1\" श्रेणीमी भया लेखहरू",
        "category-empty": "''यै श्रेणीमी हाल कोइलै पाना या मिडिया रया नाइँथिन ।''",
        "mypage": "पानो",
        "mytalk": "मेरी कुरडी",
        "anontalk": "कुरडी",
-       "navigation": "à¤\96à¥\8bà¤\9c",
-       "and": "&#32;र",
+       "navigation": "पथपà¥\8dरदरà¥\8dशन",
+       "and": "&#32;र",
        "qbfind": "तम जाण",
        "qbbrowse": "ब्राउज गर्न्या",
        "qbedit": "सम्पादन",
        "faqpage": "Project:भौत सोधिएका प्रश्नहरु",
        "actions": "कार्यहरू",
        "namespaces": "नेमस्पेस",
-       "variants": "बहà¥\81रà¥\81पहरà¥\82",
+       "variants": "बहà¥\81रà¥\81पà¤\85न",
        "navigation-heading": "नेविगेशन मेनू",
        "errorpagetitle": "त्रुटी",
        "returnto": "$1 मी फर्क।",
-       "tagline": "{{SITENAME}}बाट",
-       "help": "सहायता",
-       "search": "खोज",
-       "searchbutton": "खोज",
+       "tagline": "{{SITENAME}} बठेइ",
+       "help": "मद्दत",
+       "search": "खोजी",
+       "search-ignored-headings": " #<!-- leave this line exactly as it is --> <pre>\n# Headings that will be ignored by search.\n# Changes to this take effect as soon as the page with the heading is indexed.\n# You can force page reindexing by doing a null edit.\n# The syntax is as follows:\n#   * Everything from a \"#\" character to the end of the line is a comment.\n#   * Every non-blank line is the exact title to ignore, case and everything.\nReferences\nExternal links\nSee also\n #</pre> <!-- leave this line exactly as it is -->",
+       "searchbutton": "खोज:",
        "go": "जाने",
        "searcharticle": "जाओ",
        "history": "पाना इतिहास",
        "history_short": "पानाको इतिहास",
        "updatedmarker": "मेरो अन्तिम घुमाई पछि अद्यतन गरियाको",
-       "printableversion": "à¤\9bापà¥\8dनसà¤\95िनà¥\87 संस्करण",
+       "printableversion": "à¤\9bापà¥\8dद à¤®à¤¿à¤²à¥\8dलà¥\8dया संस्करण",
        "permalink": "स्थायी लिङ्क",
        "print": "छाप",
        "view": "अवलोकन गर",
        "unprotectthispage": "यै पानाको सुरक्षा परिवर्तन गर",
        "newpage": "नयाँ पाना",
        "talkpage": "यै पानाका बारेमी छलफल गर",
-       "talkpagelinktext": "à¤\95à¥\81रडà¥\80",
+       "talkpagelinktext": "à¤\95à¥\81रणि",
        "specialpage": "खास पानो",
-       "personaltools": "वà¥\8dयà¤\95à¥\8dतिà¤\97त à¤\94à¤\9cारहरà¥\82",
+       "personaltools": "वà¥\8dयà¤\95à¥\8dतिà¤\97त à¤\94à¤\9cारà¤\85न",
        "articlepage": "कन्टेन्ट पानो हेर",
-       "talk": "à¤\95à¥\81रडà¥\80 à¤\95ानी",
-       "views": "अवलोकन गर",
-       "toolbox": "à¤\94à¤\9cारहरà¥\82",
+       "talk": "à¤\95à¥\81रणिà¤\95ाà¤\86नी",
+       "views": "अवलोकन गर",
+       "toolbox": "à¤\94à¤\9cारà¤\85न",
        "userpage": "प्रयोगकर्ता पाना हेर्न्या",
        "projectpage": "प्रोजेक्ट पानो हेर्न्या",
        "imagepage": "चित्र पानो हेर",
        "viewhelppage": "सहायता पानो हेर्ने",
        "categorypage": "श्रेणी पानो हेर",
        "viewtalkpage": "छलफल हेर",
-       "otherlanguages": "à¤\85नà¥\8dय à¤­à¤¾à¤·à¤¾मी",
-       "redirectedfrom": "($1 à¤¬à¤¾à¤\9f à¤ªà¤ à¤¾à¤\87याà¤\95à¥\8b)",
+       "otherlanguages": "à¤\94र à¤­à¤·à¤¾à¤\85नमी",
+       "redirectedfrom": "($1 à¤¬à¤ à¥\87à¤\87 à¤ªà¥\81न:निरà¥\8dदà¥\87शित)",
        "redirectpagesub": "अनुप्रेषित पानो",
        "redirectto": "पठाएको पाना:",
-       "lastmodifiedat": "यà¥\88 à¤ªà¤¾à¤¨à¤¾à¤²à¤¾à¤\88 à¤\86नà¥\8dतिम à¤ªà¤\9fà¤\95 $2, $1 à¤®à¥\80 à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\97रिया थ्यो।",
+       "lastmodifiedat": "यà¥\88 à¤ªà¤¨à¥\8dनालाà¤\88 à¤\9bाडà¥\8dडà¥\80बाऱ $2 à¤¬à¤\9cà¥\87, $1 à¤®à¥\80 à¤¹à¥\87रफà¥\87र à¤\97रियाऽ थ्यो।",
        "viewcount": "यो पाना हेरियाको थियो {{PLURAL:$1|एकपटक|$1 पटक}}",
        "protectedpage": "सुरक्षित गर्याका पानाहरू",
-       "jumpto": "यà¥\88मà¥\80 à¤\9cाà¤\93:",
-       "jumptonavigation": "भ्रमण गर",
-       "jumptosearch": "खोज",
+       "jumpto": "यà¥\88मà¥\80 à¤«à¤\9fà¥\8dà¤\9fाà¤\95:",
+       "jumptonavigation": "भ्रमण गर",
+       "jumptosearch": "खोज",
        "view-pool-error": "माफ गर्या , अहिल सर्भरहरूमी कामको भार भौत रह्या छ।\nभौत भौत प्रयोगकर्ताहरू यै पाना हेद्या प्रयास गरी रह्या छन्।\nकृपया यो पाना पुन: हेर्नु अगाडि थोक्कै पख ।\n\n$1",
        "generic-pool-error": "माफ गर्या , अहिल सर्भरहरूमी कामको भार भौत रह्या छ।\nभौत भौत प्रयोगकर्ताहरू यै पाना हेद्या प्रयास गरी रह्या छन् ।\nकृपया यो पाना पुन: हेर्नु अगाडि थोक्कै पख ।",
        "pool-timeout": "समय सकियो बन्द गर्ने प्रतीक्षामी",
        "pool-errorunknown": "अज्ञात गल्ती",
        "pool-servererror": "पुल काउन्टर सेवा उपलब्ध नाइथिन् ($1)।",
        "poolcounter-usage-error": "प्रयोग गल्ती:$1",
-       "aboutsite": "{{SITENAME}}à¤\95à¥\8b बारेमी",
+       "aboutsite": "{{SITENAME}}à¤\86 बारेमी",
        "aboutpage": "Project:बारेमी",
        "copyright": "सामाग्री $1 अनुसार उपलब्ध छ, खुलाइएको अवस्था बाहेकका हकमी ।",
-       "copyrightpage": "{{ns:project}}:पà¥\8dरतिलिपà¥\80 à¤\85धिà¤\95ारहरà¥\82",
-       "currentevents": "आजभोलका घटनाहरू",
-       "currentevents-url": "Project:आजभोलका घटनाहरू",
-       "disclaimers": "à¤\85सà¥\8dविà¤\95ारà¥\8bà¤\95à¥\8dतिहरà¥\82",
+       "copyrightpage": "{{ns:project}}:पà¥\8dरतिलिपà¥\80 à¤\85धिà¤\95ारà¤\85न",
+       "currentevents": "आजभोलका घटना",
+       "currentevents-url": "Project:आजभोलका घटना",
+       "disclaimers": "à¤\85सà¥\8dविà¤\95ारà¥\8bà¤\95à¥\8dतà¥\80न",
        "disclaimerpage": "Project:सामान्य अस्वीकारोक्ति",
        "edithelp": "सम्पादन सहायता",
        "helppage-top-gethelp": "सहायता",
-       "mainpage": "मà¥\81à¤\96à¥\8dय à¤ªà¤¾à¤¨à¥\8b",
-       "mainpage-description": "मà¥\81à¤\96à¥\8dय à¤ªà¤¾à¤¨à¥\8b",
+       "mainpage": "मà¥\81à¤\96à¥\8dय à¤ªà¤¨à¥\8dना",
+       "mainpage-description": "मà¥\81à¤\96à¥\8dय à¤ªà¤¨à¥\8dना",
        "policy-url": "Project:निति",
        "portal": "सामाजिक पोर्टल",
        "portal-url": "Project:सामाजिक पोर्टल",
        "versionrequired": "MediaWiki संस्करण $1 चाईन्या",
        "versionrequiredtext": "ये पाना प्रयोग गर्नका लागि MediaWiki $1 संस्करण चाहिन्छ ।\nहेर  [[Special:Version|version page]]",
        "ok": "भयो",
-       "retrievedfrom": " \"$1\" बठे निकालिया",
+       "retrievedfrom": " \"$1\" बठे निकालिया",
        "youhavenewmessages": "{{PLURAL:$3|तम सित छन}} $1 ($2)।",
        "youhavenewmessagesfromusers": "तमखी लेखा {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्तान}}($2)बठे$1",
        "youhavenewmessagesmanyusers": "तमलाई धेरै प्रयोगकर्ताहरू($2) बठे $1 छ ।",
        "viewsourceold": "स्रोत हेर",
        "editlink": "सम्पादन",
        "viewsourcelink": "स्रोत हेर",
-       "editsectionhint": "खण्ड: $1 सम्पादन गर",
+       "editsectionhint": "खण्ड: $1 सम्पादन गर",
        "toc": "विषयसूची",
        "showtoc": "धेकाउन्या",
        "hidetoc": "लुकाउन्या",
        "feed-invalid": "अमान्य फिड प्रकार ग्राह्याता ।",
        "feed-unavailable": "सिन्डीकेसन फिडहरु उपलब्ध नाइथिन्",
        "site-rss-feed": "$1 आरएसएस फिड",
-       "site-atom-feed": "$1 à¤\8fà¤\9fम à¤«à¤¿ड",
+       "site-atom-feed": "$1 à¤\8fà¤\9fम à¤«à¥\80ड",
        "page-rss-feed": "\"$1\" आरएसएस फिड",
        "page-atom-feed": "\"$1\" एटम फिड",
-       "red-link-title": "$1 (पाना उपलब्ध नाइँथिन)",
+       "red-link-title": "$1 (पनà¥\8dना उपलब्ध नाइँथिन)",
        "sort-descending": "अवरोहण क्रममी मिलाउन्या",
        "sort-ascending": "आरोहण क्रममी मिलाउन्या",
-       "nstab-main": "लà¥\87à¤\96",
+       "nstab-main": "पनà¥\8dना",
        "nstab-user": "प्रयोगकर्ता पानो",
        "nstab-media": "माध्यम पाना",
        "nstab-special": "खास पानो",
        "nstab-template": "ढाँचा",
        "nstab-help": "सहायता पानो",
        "nstab-category": "श्रेणी",
-       "mainpage-nstab": "मà¥\81à¤\96à¥\8dय à¤ªà¤¾à¤¨à¥\8b",
+       "mainpage-nstab": "मà¥\81à¤\96à¥\8dय à¤ªà¤¨à¥\8dना",
        "nosuchaction": "यसो काम हैन",
        "nosuchactiontext": "URL ले खुलाएको काम मान्य छैन ।\nतमीले URL गलत टाइपगरेका हौ , वा गलत लिंकक पछाडी लागेका हुनसक्देहौ ।\nयो {{SITENAME}}ले सफ्टवेयरमी भयाको गल्ति देखायाको लै हुनसक्छ ।",
        "nosuchspecialpage": "तसो विशेष पानो छैन",
        "missingarticle-rev": "(संशोधन #: $1)",
        "missingarticle-diff": "(भिन्नता: $1, $2)",
        "readonly_lag": "डेटाबेस स्वतः बन्द गरिया छ जबकि अधिनस्थ डेटाबेस सर्वरले मूल पहिल्याउँनाछ।",
+       "nonwrite-api-promise-error": "'Promise-Non-Write-API-Action' HTTP हेडर पठाइयाथ्यो तर अनुरोध API write module खिलाइ थ्यो।",
        "internalerror": "भित्रका गल्ती",
        "internalerror_info": "भित्रका गल्ती: $1",
        "internalerror-fatal-exception": "प्रकारको गम्भीर अपवाद \"$1\"",
        "title-invalid-interwiki": "अनुरोध गरियाको शिर्षकमी अन्तर विकि लिङ्क छ जइलाई शिर्षकमी प्रयोग गद्द नाइपाइनो ।",
        "title-invalid-talk-namespace": "निवेदन गरियाको पानाको शिर्षकले उपलब्ध नभएका कुरडी पानालाई सन्दर्भको रूपमी राख्याको छ ।",
        "title-invalid-characters": "निवेदन गरियाको यै पानाको शिर्षकमी अवैध अक्षर रयाको छः \"$1\" ।",
+       "title-invalid-magic-tilde": "अनुरोध अरिया: पन्ना: शीर्षकमी अमान्य म्याजिक टिल्ड शृङ्खला छ (<nowiki>~~~</nowiki>)।",
+       "title-invalid-too-long": "अनुरोध अरिया: पन्ना: शीर्षक भौत लामु छ। यो UTF-8 इनकोडिङमी $1 {{PLURAL:$1|byte|bytes}} है लामु हुनु हुनैन।",
        "title-invalid-leading-colon": "निवेदन गरिया पृष्ठको शिर्षकको शुरूमी अवैध कोलोन रया छ ।",
        "perfcached": "तलका डाटाहरू क्याचमी रया कुराहरू हुन्। अपटुडेट नहुन लाई सक्दान। बर्ति {{PLURAL:$1|नतिजा|$1 नतिजाहरू}} क्याचमी उपलब्ध छ।",
        "perfcachedts": "तलतिरको आँकडा क्याच हो र $1 पहिला अद्यतन गरिया थ्यो। येई क्याचमी उपलब्ध {{PLURAL:$4|एउटा कारण हो|$4 कारणहरू हुन्}}।",
+       "querypage-no-updates": "येइ पन्ना: अद्दतन कार्य ऐल़ निस्क्रिय अरीरैछ।\nयाँ को डाटा ऐलखिलाइ ताजो अरिन्या आथिन।",
        "viewsource": "स्रोत हेर",
        "viewsource-title": " $1 को स्रोत हेर",
        "actionthrottled": "कार्य रोकिईयो",
        "viewsourcetext": "तम ये पृष्ठको स्रोत हेद्दु सकुन्छौ और उईको नक्कल उताद्दु सकुन्छौ |",
        "viewyourtext": "यै पानामी रह्याका '''तमरा सम्पादनहरू''' हेद्द या प्रतिलिपी गद्द सक्द्या हौ :",
        "editinginterface": "<strong>चेतावनी:</strong> तमी यै पानालाई सम्पादन गद्द लाग्याछौ, जनले सफ्टवेयरको लागि \nइन्टरफेस सामग्रीहरू प्रदान गरन्छ।\nयै पानामी गरियाको परिवर्तनले यै विकिमी अरु प्रयोगकर्तानको इन्टरफेसको प्रदर्शनमी प्रभाव पडन्छ ।",
+       "translateinterface": "सप्पै विकिइनखिलाइ अनुवाद थप्दाइ या बदेल्लाइ, कृपया [https://translatewiki.net/ translatewiki.net]को प्रयोग अर:, मिडियाविकि क्षेत्रीयकरण परियोजना:।",
        "namespaceprotected": "तमलाई '''$1'''  नेमस्पेसमी रह्याका पानाहरू सम्पादन गद्या अनुमति छैन ।",
        "customcssprotected": "तमलाई यो  पानो सम्पादन गद्दे अनुमति छैन, किनकी यैमी कुनै अर्को प्रयोगकर्ताको व्यक्तिगत अभिरुचीहरू संग्रहित छन् ।",
        "customjsprotected": "तमलाई यो जाभास्कृप्ट पानो सम्पादन गद्दे अनुमति छैन, किनकी यैमी कुनै अर्को प्रयोगकर्ताको व्यक्तिगत अभिरुचीहरू संग्रहित छन् ।",
        "mypreferencesprotected": "तमसंग तमरो अभिरुचीहरू सम्पादन ग्द्दे अनुमती छैन |",
        "ns-specialprotected": "विशेष पृष्ठहरू सम्पादन अद्दु नाइँ सकिनो।",
        "titleprotected": "[[User:$1|$1]]द्वारा ये शीर्षक निर्माणहुनबठे जोगाइया छ।\nकारण <em>$2</em> हो ।",
-       "filereadonlyerror": "फाइल \"$1\" लाई परिवर्तन अद्दु नाइँ सकिनो क्याईकि फाइल भण्डार  \"$2\" केवल पढ्ने स्थिति (read-only mode)मी छ।\n\nयेलाई सुरक्षित गर्ने प्रवन्धकले  यो कारण दियाकाछन् : ''$3''।",
+       "filereadonlyerror": "\"$1\" फाइललाई परिवर्तन अद्दु नाइँ सकिनो क्याईकि फाइल भण्डार \"$2\" केवल पड्ड्या स्थिति (read-only mode)मी छ।\n\nयेलाई सुरक्षित अद्द्या प्रवन्धकले यो कारण दीराइछ: ''$3''।",
+       "invalidtitle-knownnamespace": "\"$2\" नाउँबार रे \"$3\" पाठ भया: अमान्य शीर्षक",
+       "invalidtitle-unknownnamespace": "अपछ्याणो नाउँबार अङ्क $1 रे पाठ \"$2\" भया: अमान्य शीर्षक",
        "exception-nologin": "प्रवेश (लग ईन) नगरिएको",
+       "exception-nologin-text": "येए पन्नालाई चलुनाइ या केयि काम खिलाइ सक्षम हुनाइ लगइन अर:।",
+       "virus-badscanner": "गट्टी मिलजुल: अपछ्याणो भाइरस खोज्ज्या: <em>$1</em>",
        "virus-scanfailed": "जँचाई असफल(कोड $1)",
        "virus-unknownscanner": "थानभया एन्टीभाइरस:",
        "logouttext": "<strong>तमी अहिल बाहिर निस्क्याका  छौ।</strong>\n\nयाद राख्या तमीले ब्राउजरको क्याच खालि नगर्यासम्म कुनै पानाहरूमी तमी अझैं प्रवेश गरिरख्याको धेकाउन सक्छ।",
        "createacct-yourpassword-ph": "पासवर्ड लेख",
        "yourpasswordagain": "पासवर्ड फेरि टाईप गर",
        "createacct-yourpasswordagain": "पासवर्ड निश्चित गर",
-       "createacct-yourpasswordagain-ph": "à¤\86à¤\9cà¥\80 à¤ªà¤¾à¤¸à¤µà¤°à¥\8dड à¤²à¥\87à¤\96",
+       "createacct-yourpasswordagain-ph": "à¤\86à¤\81à¤\9cि à¤ªà¤¾à¤¸à¤µà¤°à¥\8dड à¤­à¤°à¤½",
        "userlogin-remembermypassword": "मुलाई अघाडी झान्या काम गराइराख्या",
        "userlogin-signwithsecure": "सुक्षित जडान प्रयोग गद्द्या",
+       "cannotlogin-text": "येइमी लगइन सम्भव नाइथिन।",
        "cannotloginnow-title": "अईल भितर झान नाइँ पाईनो",
        "cannotloginnow-text": "भितर जान असंभव छ जब प्रयोग $1|",
+       "cannotcreateaccount-title": "खाता बनौन नाइसक्दो",
        "yourdomainname": "तमरो ज्ञानक्षेत्र(डोमेन):",
        "password-change-forbidden": "ये विकिमी पासवर्ड परिवर्तन गर्न सक्नुहुन्न।",
        "login": "प्रवेश (लगईन)",
        "userlogin-resetpassword-link": "पासवर्ड भुलिगया?",
        "userlogin-helplink2": "प्रवेश गद्दलाई सहयोग",
        "userlogin-loggedin": "तमी {{GENDER:$1|$1}}को रूपमी प्रवेश (लग इन) भइ सक्यौ ।\nअर्को प्रयोगकर्ताको रूपमी प्रवेश (लग इन) गर्न तलको फारम प्रयोग गर ।",
+       "userlogin-reauth": "तम {{GENDER:$1|$1}} हो भणिबर पुष्टि अद्दाइ दोसर्‍याँ लगइन अर:।",
        "userlogin-createanother": "दोसरो खाता खोल",
        "createacct-emailrequired": "इमेल ठेगाना",
        "createacct-emailoptional": "इमेल ठेगाना (ऐच्छिक)",
-       "createacct-email-ph": "तमरà¥\8b à¤\87मà¥\87ल à¤ à¥\87à¤\97ाना à¤­à¤°à¤¯à¤¾",
+       "createacct-email-ph": "तमरà¥\8b à¤\87मà¥\87ल à¤ à¥\87à¤\97ाना à¤­à¤°à¤½",
        "createacct-another-email-ph": "इमेल ठेगाना भर",
        "createacct-realname": "वास्तविक नाम (ऐच्छिक)",
        "createaccountreason": "कारण:",
        "createacct-reason": "कारण",
        "createacct-reason-ph": "क्याई तम नयाँ खाता खोल्ला छौ?",
-       "createacct-submit": "तमरà¥\8b à¤\96ाता à¤¸à¤¿à¤°à¥\8dà¤\9cना à¤\97र",
+       "createacct-submit": "तमरà¥\8b à¤\96ाता à¤¬à¤¨à¤¾à¤½",
        "createacct-another-submit": "खाता खोल",
        "createacct-continue-submit": "खाता खोल्लु जारि राख",
        "createacct-another-continue-submit": "खाता खोल्लु जारि राख",
        "pt-login": "प्रवेश (लग ईन)",
        "pt-login-button": "प्रवेश",
        "pt-login-continue-button": "प्रवेश जारी राख",
-       "pt-createaccount": "नयाँ खाता खोल",
+       "pt-createaccount": "नयाँ खाता खोल:",
        "pt-userlogout": "बाहिर निस्कन्या (लग आउट)",
        "php-mail-error-unknown": "PHP मेल() क्रियामा अज्ञात गल्ती",
        "user-mail-no-addy": "इमेल ठेगाना बिनाई इमेल पठाउन खोजिया थ्यो।",
        "changeemail-password": "तमरो {{SITENAME}} पासवर्ड:",
        "changeemail-submit": "इमेल परिवर्तन गद्या",
        "changeemail-throttled": "तमले अलै भौत फेर प्रवेशका निम्ति प्रयास गरया छौ।\nकृपया $1 पर्खेर मात्र प्रयास गर।",
+       "changeemail-nochange": "कृपया नौलो जुदोइ इमेल ठेगाना राख:।",
        "resettokens": "टोकन पूर्वरुपमी फर्काउन्या",
        "resettokens-text": "जो टोकन तमरो खातासँग सम्बद्ध केहि विशिष्ट व्यक्तिगत जानकारी प्रदान गर्छन, तम त्यसलाई यहाँ रिसेट गद्द सक्द्या हौ।\n\nयदि तमले तिनलाई भुलवस कैकनै देखाईदिया छौ वा तमरो खाता ह्याक भइसक्याको छ भन्या तम यसलाई रिसेट गर्या ।",
        "resettokens-no-tokens": "पूर्वरुमी फर्काउन्या कोई लै टोकन नाइथिन् ।",
        "anontalkpagetext": "----''यो कुरडी पानो अज्ञात प्रयोगकर्ताको हो जनले अहिलसम्म खाता बनायाकै छैन, अथवा जनले यै पानाको उपयोग गर्दैन।\nयस कारण हामीले उनलाई उनरो आइ पी (IP) ठेगानाले चिन्न सकन्छौ। \nयस्तो आइ पी (IP) ठेगाना धेरै प्रयोगकर्तानको साझा हुनसकन्छ ।\nयदि तमी अज्ञात प्रयोगकर्ता हौ र तमलाई अचाहिँदो टिप्पणी भयाको अनुभव गद्दा छौ भण्या भविष्यमी अन्य अज्ञात प्रयोगकर्तासँगको भ्रमबाट बाँच्न कृपया [[Special:CreateAccount|खाता खोल]] अथवा [[Special:UserLogin|प्रवेश गर]] ''",
        "noarticletext": "यै लेखमी अहिल क्यै पन पाठ नाइथी  ।\nतमले और पृष्ठमी\n[[Special:Search/{{PAGENAME}}|यस पृष्ठको शीर्षककी लेखा खोज]] गद्द सकन्छौ ।\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} पाना सम्बन्धित ढड्डामी खोज],\nवा [{{fullurl:{{FULLPAGENAME}}|action=edit}}  यै पानालाई सम्पादन गद्या]</span>.",
        "noarticletext-nopermission": "यै लेखमी अहिल केइ पन पाठ नाइथी  ।\nतमले और पानामी\n[[Special:Search/{{PAGENAME}}|यै पानाको शीर्षककी लेखा खोज]] गद्द सकन्छौ ।\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} पाना सम्बन्धित ढड्डामी खोज्न],\nवा [{{fullurl:{{FULLPAGENAME}}|action=edit}}  यै पानालाई सम्पादन गद्द] सकन्छौ</span>.",
+       "userpage-userdoesnotexist": "\"$1\" प्रयोगकर्ता खाता दर्ता अरीया: आथिन।\nयेइ पान्नो बनुन/सम्पादन अद्द चाहन्छ: भण्या विचार अर:।",
        "userpage-userdoesnotexist-view": "प्रयोगकर्ता खाता \"$1\" दर्ता गरिया छैन।",
+       "blocked-notice-logextract": "यो प्रयोगकर्ता अच्याल प्रतिवन्धित छ।\nसब है पछा: प्रतिबन्ध लग प्रविष्टि सन्दर्भ खिलाइ तल्तिर दियीरैछ:",
+       "sitecsspreview": "<strong>येइ CSSलाई तम पूर्वावलोकन मात्तरी अद्दाछ: भणिबर फाम अर:।\nयो आँजि सङ्ग्रह अरिया: आथिन। </strong>",
+       "sitejspreview": "<strong>येइ जावास्क्रिप्ट कोडलाई तम पूर्वावलोकन मात्तरी अद्दाछ: भणिबर फाम अर:।\nयो आँजि सङ्ग्रह अरिया: आथिन। </strong>",
        "userinvalidcssjstitle": "<strong>चेतावनी:</strong> यहाँ कोइपनि \"$1\" नामको खोल नाइथिन् ।\nप्रचलित .css तथा .js पानाहरूले निम्नपद शीर्षक प्रयोग गद्दान्, जस्तै {{ns:user}}:Foo/Vector.css को सट्टामी {{ns:user}}:Foo/vector.css",
        "updated": "नौला",
        "note": "'''सूचना:'''",
+       "previewnote": "<strong>फाम अर: कि यो यक पूर्वावलोकन मात्तरी हो।</strong>\nतमले अर्‍या फेरबदेली आँजि सङ्ग्रहित भया: आथिन!",
        "continue-editing": "सम्पादन क्षेत्रमी जाओ",
-       "editing": "$1 à¤¸à¤®à¥\8dपादन à¤\97रिदà¥\88",
+       "editing": "$1 à¤¸à¤®à¥\8dपादन à¤\85रà¥\80नà¥\8dनाà¤\9b़",
        "creating": "$1 बनाइँदै",
        "editingsection": "$1 (खण्ड) सम्पादन गरिदै",
        "editingcomment": "$1 सम्पादन गर्दै(नयाँ खण्ड)",
        "template-protected": "(सुरक्षित)",
        "template-semiprotected": "(अर्ध-सुरक्षित)",
        "hiddencategories": "यो पानो निम्न {{PLURAL:$1|1 लुकाइयाको श्रेणी|$1 लुकाइयाका श्रेणीहरू}}को हिस्सादार(सदस्य) हो :",
+       "nocreate-loggedin": "नौला पन्ना बनुनाइ तम सित अधिकार आथिन।",
        "sectioneditnotsupported-title": "खण्ड सम्पादन असमर्थित",
        "sectioneditnotsupported-text": "ये पृष्ठमी खण्ड सम्पादन असमर्थित",
        "permissionserrors": "अधिकारमी त्रुटी",
+       "permissionserrorstext": "तइ काम अद्दाइ तम सित अधिकार आथिन, यिन {{PLURAL:$1|कारण|कारणअन}}ले अद्दा:",
        "permissionserrorstext-withaction": "$2 कि लेखा तमलाईँ अनुमति नाइथिन , यिन {{PLURAL:$1|कारणले|कारणहरुले}} गद्दा :",
        "moveddeleted-notice": "पानो मेटियाको छ।\nमेटियाका और सारियाका पानाहरूको सूची तल्तिर सन्दर्भखी लेखा दियाको छ।",
        "log-fulllog": "पूरा लग हेर",
        "viewprevnext": "हेर ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "''' \"[[:$1]]\" नाम गरया पाना  ये विकीमी रह्या छ'''",
        "searchmenu-new": "<strong>\"[[:$1]]\"  पानो इसै विकिमी बनाओ !</strong> {{PLURAL:$2|0=|तमले खोज अरी भेटियाको पानो पन सङ्ङै जोड्या काम अर ।|तमरो खोज परिणाम पन हेर।}}",
-       "searchprofile-articles": "सामà¤\97à¥\8dरà¥\80 à¤ªà¤¾à¤¨à¤¾à¤¹à¤°à¥\82",
+       "searchprofile-articles": "सामà¤\97à¥\8dरà¥\80 à¤ªà¤¨à¥\8dनाà¤\85न",
        "searchprofile-images": "मल्टिमिडिया(श्रव्य दृश्य)",
        "searchprofile-everything": "सबै थोक",
        "searchprofile-advanced": "उन्नत",
        "searchprofile-articles-tooltip": "$1 मी खोज्या",
-       "searchprofile-images-tooltip": "फाइलहरू खोज्ज्या",
+       "searchprofile-images-tooltip": "फाइल कि लेखा खोजऽ",
        "searchprofile-everything-tooltip": "सबै सामग्री खोज्या (वार्तालाप लै )",
        "searchprofile-advanced-tooltip": "अनुकुल नेमस्पेसमा खोज्या",
-       "search-result-size": "$1 ({{PLURAL:$2|1 à¤¶à¤¬à¥\8dद|$2 à¤¶à¤¬à¥\8dदहरà¥\82}})",
+       "search-result-size": "$1 ({{PLURAL:$2|1 à¤\86à¤\81à¤\96र|$2 à¤\86à¤\81à¤\96र}})",
        "search-result-category-size": "{{PLURAL:$1|एक सदस्य|$1 सदस्यहरू}} ({{PLURAL:$2|1 उपश्रेणी|$2  उपश्रेणीहरू}}, {{PLURAL:$3|एउटा फाइल|$3 फाइलहरू}})",
        "search-redirect": "(जान्या $1)",
        "search-section": "(खण्ड $1)",
        "search-interwiki-more": "(आजी)",
        "search-relatedarticle": "सम्बन्धित",
        "searchrelated": "सम्बन्धित",
-       "searchall": "सबै",
+       "searchall": "सपà¥\8dपै",
        "showingresults": "धेखाउँदै  {{PLURAL:$1|'''१''' नतिजा|'''$1''' नतिजाहरू }} , #'''$2''' बठे सुरुहुन्या ।",
        "showingresultsinrange": "देखाई रह्या छ{{PLURAL:$1|<strong>1</strong> result|<strong>$1</strong> परिणाम}} सम्म पहुँच  #<strong>$2</strong> देखि #<strong>$3</strong> मी।",
        "search-showingresults": "{{PLURAL:$4|<strong>$3</strong> मै बठे <strong>$1</strong> परिणाम|<strong>$3</strong> मै बठे परिणाम <strong>$1 - $2</strong>}}",
        "recentchanges-legend": "अच्यालैका परिवर्तन विकल्पहरू",
        "recentchanges-summary": "विकिका यैल्लैका फेरबदललाई यै पानामि पहिल्याउन्या",
        "recentchanges-label-newpage": "यै सम्पादनले नौलो पानो बनायाको छ",
-       "recentchanges-label-minor": "यो नानो सम्पादन हो",
+       "recentchanges-label-minor": "यà¥\8b à¤¨à¤¾à¤½à¤¨à¥\8b à¤¸à¤®à¥\8dपादन à¤¹à¥\8b",
        "recentchanges-label-bot": "यो सम्पादन बोटबठे गरियाको थ्यो",
        "recentchanges-label-unpatrolled": "यो सम्पादन यैलसम्म गस्ती गरियाको नाइथी",
        "recentchanges-label-plusminus": "यति बाइटहरू संख्याले पानाको आकार फेरबदल  भयाको छ",
        "rclistfrom": "$3 $2 देखिका नयाँ परिवर्तनहरू देखाउन्या",
        "rcshowhideminor": "$1 सानतिनो सम्पादन",
        "rcshowhideminor-show": "धेकाइदिय",
-       "rcshowhideminor-hide": "लà¥\81à¤\95ाà¤\89नà¥\8dया",
+       "rcshowhideminor-hide": "लà¥\81à¤\95ाऽ",
        "rcshowhidebots": "$1 बोटहरू",
        "rcshowhidebots-show": "धेकाइदिय",
        "rcshowhidebots-hide": "लुकाइदिय",
        "rcshowhideliu": "$1 दर्ता अर्याका प्रयोगकर्ताहरू",
        "rcshowhideliu-hide": "लुकाउन्या",
        "rcshowhideanons": "$1 नपछेण्याका प्रयोगकर्ता",
-       "rcshowhideanons-show": "धà¥\87à¤\95ाà¤\87दिय",
-       "rcshowhideanons-hide": "लà¥\81à¤\95ाà¤\89नà¥\8dया",
+       "rcshowhideanons-show": "धà¥\87à¤\95ाऽ",
+       "rcshowhideanons-hide": "लà¥\81à¤\95ाऽ",
        "rcshowhidepatr": "$1 पट्रोल गर्याका सम्पादनहरू",
        "rcshowhidemine": "$1 मेरा सम्पादनहरू",
        "rcshowhidemine-show": "धेकाइदिय",
-       "rcshowhidemine-hide": "लà¥\81à¤\95ाà¤\87दिय",
+       "rcshowhidemine-hide": "लà¥\81à¤\95ाऽ",
        "rcshowhidecategorization-show": "धेकाउन्या",
        "rcshowhidecategorization-hide": "लुकाउन्या",
        "rclinks": "पछिल्ला $1 परिवर्तनहरू पछाडिका $2 दिनहरूमी<br />$3",
        "newpageletter": "नौ",
        "boteditletter": "बो",
        "rc_categories": "श्रेणीहरूमी सीमित (\"|\" ले छुट्याओ)",
-       "rc-change-size-new": "$1 {{PLURAL:$1|बाà¤\87à¤\9f|बाà¤\87à¤\9fस}}फà¥\87रबदलपाछा",
+       "rc-change-size-new": "$1 {{PLURAL:$1|बाà¤\87à¤\9f|बाà¤\87à¤\9fà¥\8dस}}फà¥\87रबदल पाछा",
        "recentchangeslinked": "सम्बन्धित फेरबदल",
        "recentchangeslinked-toolbox": "सम्बन्धित फेरबदल",
        "recentchangeslinked-title": "\"$1\" सित सम्बन्धित परिवर्तन",
        "recentchangeslinked-summary": "यो सूची निर्दिष्ट पाना (वा निर्दिष्ट श्रेणी)सित जोडियाका अल्लै परिवर्तन भयाका पानाको  हो। [[Special:Watchlist|तमरो ध्यानसूची]]का पानाहरू <strong>गाढा अक्षरमी</strong> छन्।",
        "recentchangeslinked-page": "पाना नाम:",
        "recentchangeslinked-to": "यैको सट्टा यो पानासित जोडियाका पानानको परिवर्तन धेकाउन्या",
-       "upload": "à¤\9aितà¥\8dर à¤\85पलà¥\8bड à¤\97र",
+       "upload": "फाà¤\87ल à¤\85पलà¥\8bड à¤\97रऽ",
        "uploadbtn": "फाइल अपलोड गर्न्या",
        "upload-recreate-warning": "'''चेतावनी: त्यस नाममी रह्याका फाइलहरू सारियाको या हटायाको छ।'''\n\nयै पानाको सारियाको र हटायाको लग तमरो सहजताको लागि दियाको छ।",
        "filedesc": "सारांश:",
        "large-file": "यो सिफारिस गर्याछकि फाइलहरूको आकार $1 भन्दा ठूला हुनु हुँदैन;\nयै फाइलको आकार $2 छ ।",
        "emptyfile": "तमीले अपलोड गर्याको फाइल रित्तो छ ।\nयो फाइल नाम गलत राख्याका कारणले भयाको हुनसकन्छ\nयो फाइल साँच्चै अपलोड गद्दे कुरडीमी निश्चित होइजाओ ।",
        "fileexists": "यै नामको फाइल पैल्ली नैं छ, यदि तम परिवर्तन गद्या कुरडीमू सुनिश्चित छैनौ भण्या कृपया <strong>[[:$1]]</strong> जाँच गर।\n[[$1|thumb]]",
+       "fileexists-no-change": "अपलोड <strong>[[:$1]]</strong>का अच्यालआ संस्करणो ठ्याक्कै नकल हो।",
+       "fileexists-duplicate-version": "अपलोड <strong>[[:$1]]</strong> को {{PLURAL:$2|पुरानु संस्करण|पुरानु संस्करणअन}}ओ नकल हो।",
        "filewasdeleted": "यै नामको एक फाइल पहिली पनि अपलोड गरिबर पछि हटाई सकियाको छ।\nपुनः अपलोड गद्दु पूर्व तम $1 लाई निक्करी जाँच गर ।",
        "upload-dialog-title": "चित्र अपलोड गर",
        "upload-dialog-button-cancel": "रद्द",
        "uploadstash-nofiles": "तमरा कोइ पनि स्टाश गर्याका फाइलहरू नाइथिन् ।",
        "uploadstash-badtoken": "त्यो कार्य असफलभयो , सायद तमरो सम्पादन अधिकार समाप्त भयो । पुन: प्रयास गर ।",
        "uploadstash-refresh": "फाइलहरूको सूची ताजा गर्न्या",
-       "license-header": "à¤\95à¥\8bà¤\87 à¤\95à¥\87à¤\87 à¤¨à¤¾à¤\87थिन",
+       "license-header": "à¤\86à¤\9cà¥\8dà¤\9eापतà¥\8dर à¤¦à¤¿à¤¨à¥\8dनाà¤\9b़",
        "listfiles-summary": "यै खास पानाले अपलोड गर्याका सबै फाइलहरू धेकाउन्छ ।",
        "imgfile": "चित्र",
        "listfiles_count": "संस्करणहरू",
        "filehist-thumb": "थम्बनेल",
        "filehist-thumbtext": "थम्बनेल $1 संस्करणको रुपमी",
        "filehist-user": "प्रयोगकर्ता",
-       "filehist-dimensions": "à¤\86à¤\95ारहरà¥\82",
+       "filehist-dimensions": "à¤\86याम",
        "filehist-comment": "टिप्पणी",
        "imagelinks": "फाइलको प्रयोगहरु",
        "linkstoimage": "यै चित्रमी निम्न{{PLURAL:$1|पाना जोडिनान{{PLURAL:$1|}}|$1 पानाहरू जोडिनान्}}:",
        "filedelete-intro-old": "तमी <strong>[[Media:$1|$1]]</strong> को संस्करणलाई [$4 $3, $2] हुन्या गरि मेट्ट लाग्याछौ ।",
        "filedelete-maintenance": "रखरखाव चलिरह्याको हुनाले अस्थायी रुपमी फाइलहरू मेट्ट्या र मेट्याकोलाई पुनर्बहाली गर्न निष्क्रिय गरियाकोछ।",
        "mimesearch-summary": "MIME-प्रकार अनुसार फाइलहरू खोज्न यै पानाको प्रयोग गद्द सकिन्याछ ।\nइनपुट: फाइलको प्रकार/उपप्रकार, उदा. <code>image/jpeg</code>।",
-       "randompage": "à¤\95à¥\8bà¤\87 à¤\8fà¤\95 à¤²à¥\87à¤\96",
+       "randompage": "à¤\95à¥\8dरमरहित à¤ªà¤¨à¥\8dना",
        "statistics-header-pages": "पानानको तथ्याङ्क",
        "statistics-header-edits": "सम्पादनहरूको तथ्याङ्क",
        "statistics-files": "अपलोड गर्याका फाइलहरू",
        "deadendpagestext": "निम्न पानाहरू {{SITENAME}}मी रह्याका अरु पानाहरूसँग जोडिदाइनन् ।",
        "protectedpagesempty": "यै बेला यी नियम बठे कुनै पाना लै शुरक्षित नाइथिन्",
        "usereditcount": "$1 {{PLURAL:$1|सम्पादन|सम्पादनहरू}}",
-       "newpages": "नयाà¤\81 à¤ªà¤¾à¤¨à¤¾à¤¹à¤°à¥\82",
+       "newpages": "नà¥\8cला à¤ªà¤¨à¥\8dनाà¤\85न",
        "move": "नाम बदल",
        "movethispage": "पानाको नाम बदल्न्या",
        "notargettext": "यै कार्यका लेखाई तमीले कुनै लक्षित पानो वा प्रयोगकर्ता निर्दिष्ट गर्याको छैनौ ।",
        "booksources-text": "तल दियाको सूची नौला तथा पूराना किताब बेच्न्या लगायत तमीले खोज्याका किताबका बारेमी थप जानकारी भयाका अन्य साइटका लिंकहरू हुन् ।",
        "log": "लगहरू",
        "all-logs-page": "सब्बै सार्वजनिक लगहरू",
-       "allarticles": "सबà¥\8dबà¥\88 à¤²à¥\87à¤\96हरà¥\82",
-       "allpagessubmit": "à¤\9cानà¥\8dया",
+       "allarticles": "सपà¥\8dपà¥\88 à¤ªà¤¨à¥\8dनाà¤\85न",
+       "allpagessubmit": "à¤\9cाऽ",
        "allpagesprefix": "यी सुरुका अक्षरसहितका पानाहरू हेद्या:",
        "categories": "श्रेणीहरू",
        "listusers-noresult": "प्रयोगकर्ता भेटियानन्",
        "month": "महिना बठे (लै पैल्ली):",
        "year": "वर्ष बठे( लौ पैल्ली):",
        "sp-contributions-toponly": "नवीनतम संशोधनका सम्पादनहरू मात्र धेकाओ",
-       "whatlinkshere": "याà¤\81à¤\96ाà¤\87 à¤\95à¥\80 à¤\9cà¥\81डन्छ",
-       "whatlinkshere-title": "$1 à¤¸à¤¿à¤¤ à¤\9cà¥\8bडियाà¤\95ा à¤ªà¤¾à¤¨à¤¾à¤¹à¤°à¥\82",
+       "whatlinkshere": "याà¤\81à¤\96ाà¤\87 à¤\95ि à¤\9cà¥\8bणà¥\80न्छ",
+       "whatlinkshere-title": "$1 à¤¸à¤¿à¤¤ à¤\9cà¥\8bडियाऽ à¤ªà¤¨à¥\8dनाà¤\85न",
        "whatlinkshere-page": "पानो",
        "linkshere": "निम्न पानाहरू '''[[:$1]]''' मी जुडन्छ :",
        "nolinkshere-ns": "चुनियाको नामस्थानमी '''[[:$1]]''' सित जुड्न्या पानाहरू नाइथिन्।",
        "whatlinkshere-links": "← लिंकहरू",
        "whatlinkshere-hideredirs": "$1 पुन:निर्देशित हुन्छ",
        "whatlinkshere-hidetrans": "$1 सम्मील",
-       "whatlinkshere-hidelinks": "$1 लिङ्कहरू",
+       "whatlinkshere-hidelinks": "$1 लिङ्क",
        "whatlinkshere-hideimages": "$1 फाइल लिंकहरू",
        "whatlinkshere-filters": "छानियाका",
        "ipbreason-dropdown": "* ब्लक गर्नुका समान्य कारणहरू\n** झूटो सूचना दियाको\n** पानानबठे सामाग्रीहरू हटायाको\n** बाहिरी जालक्षेत्र (sites)सित नचाहिंदो लिङ्क गर्याको \n** पानानमी बकवास/गाली-गलौच हाल्याको\n** भै धेकाउने व्यवहार/उत्पीडन (सताउने कार्य) गर्याको\n** धेरै गलत खाताहरू बनायाको\n** प्रयोगकर्ता नाम अस्वीकार्य",
        "ipblocklist": "ब्लक गर्याका प्रयोगकर्ताहरू",
        "ipblocklist-legend": "ब्लक गर्याका प्रयोगकर्ताहरू खोज",
        "blocklink": "रोक्न्या",
-       "contribslink": "यà¥\8bà¤\97दानहरà¥\82",
+       "contribslink": "यà¥\8bà¤\97दानà¤\85न",
        "block-log-flags-anononly": "नाम नभयाका प्रयोकर्ताहरू मात्र",
        "proxyblockreason": "तमरो IP ठेगानामी रोक लगायाको छ किनकी यो खुला प्रोक्सी हो ।\nकृपया तमरो इन्टरनेट सेवा प्रदायक या प्राविधिक सहायतासँग सम्पर्क गरीबर यै सुरक्षा समस्याका बारेमी जानकारी गराओ ।",
        "sorbsreason": "तमरो IP ठेगाना खुल्ला प्रोक्सीको रुपमी  DNSBL मा सूचीकरण गरिएको छ यैलाई{{SITENAME}}ले प्रयोगमी ल्यायाको छ।",
        "import-logentry-upload-detail": "$1 {{PLURAL:$1|संशोधन|संशोधनहरू}} आयात भयो",
        "tooltip-pt-userpage": "{{GENDER:|तमरो प्रयोगकर्ता}} पान्नो",
        "tooltip-pt-anonuserpage": "तमी जो IP ठेगानाको रुपमी सम्पादन गद्दै छौ , त्यैको प्रयोगकर्ता पानो निम्न छ :",
-       "tooltip-pt-mytalk": "{{GENDER:|तमरà¥\8b}} à¤\95à¥\81रडà¥\80à¤\95ानà¥\80 à¤ªà¤¾à¤¨à¥\8dनà¥\8b",
+       "tooltip-pt-mytalk": "{{GENDER:|तमरो}} कुरडीकानी पानो",
        "tooltip-pt-preferences": "{{GENDER:|तमरी}} अभिरुचि",
        "tooltip-pt-watchlist": "पृष्ठहरूको सूची जैका फेरबदलहरुलाई तमले पहरा गरिराखेका छौ ।",
        "tooltip-pt-mycontris": "{{GENDER:|तमरा}} योगदानअनऐ सूची",
-       "tooltip-pt-login": "तमलाई प्रवेशगद्द सुझाव दिइन्छ ; याद अर यो जरुरी आथिन भण्या ।",
+       "tooltip-pt-login": "तमलाई प्रवेशगद्द सुझाव दिइन्छ ; याद अर यो जरुरी आथिन भण्या ।",
        "tooltip-pt-logout": "बाहिर निस्कन्या (लग आउट)",
-       "tooltip-pt-createaccount": "तमलाई खाता बनौन लै लग इन अद्द हम हौसला अद्दाउ; काइकि, यो अनिवार्य नाइथी भण्या ।",
-       "tooltip-ca-talk": "सामाà¤\97à¥\8dरà¥\80 à¤ªà¥\83षà¥\8dठबारà¥\87मà¥\80 à¤\9bलफल",
-       "tooltip-ca-edit": "ये पाना सम्पादन गर",
+       "tooltip-pt-createaccount": "तमलाई खाता बनौन लै लगइन अद्द लै हम हौसला अद्दाउ; यद्यपि, यो अनिवार्य नाइथीन।",
+       "tooltip-ca-talk": "सामाà¤\97à¥\8dरà¥\80 à¤ªà¥\83षà¥\8dठबारà¥\87मà¥\80 à¤\95à¥\81रणिà¤\95ाà¤\86नà¥\80",
+       "tooltip-ca-edit": "येइ पन्ना सम्पादन गरऽ",
        "tooltip-ca-addsection": "नयाँ खण्ड सुरु अरिदिय",
        "tooltip-ca-viewsource": "यो पानो सुरक्षित अरियाको छ। यैको श्रोत हेद्द सकन्छौ ।",
-       "tooltip-ca-history": "यà¥\88 à¤ªà¥\83षà¥\8dठà¤\95ा à¤ªà¥\88लà¥\8dलिà¤\95ा à¤ªà¥\81नरावलà¥\8bà¤\95नहरà¥\81",
+       "tooltip-ca-history": "यà¥\88 à¤ªà¤¨à¥\8dनाऽ à¤ªà¥\88लà¥\8dलिà¤\95ा à¤ªà¥\81नरावलà¥\8bà¤\95नà¤\85न",
        "tooltip-ca-undelete": "मेट्याको भया पनि यै पानाको सम्पादनहरू पुन:प्राप्त गर",
        "tooltip-ca-move": "यो पानालाई अर्खिठौर सार",
        "tooltip-ca-watch": "यै पानालाई तमरा ध्यानसूचीमि थपिदिय",
-       "tooltip-search": "{{SITENAME}}मी खोज",
-       "tooltip-search-go": "यदि à¤¯à¥\8b à¤¨à¤¾à¤®à¤\95à¥\8b à¤ªà¥\83षà¥\8dठ à¤°à¤¯à¤¾à¤\95à¥\8b à¤\9b à¤­à¤£à¥\8dया à¤¤à¥\88मà¥\80 à¤\9cानà¥\8dया ।",
-       "tooltip-search-fulltext": "यà¥\88 à¤ªà¤¾à¤ à¤\95ा à¤²à¤¾à¤\97ि à¤ªà¤¾à¤¨à¤¾मी खोज",
-       "tooltip-p-logo": "à¤\96ास à¤ªà¤¾à¤¨à¥\8b",
+       "tooltip-search": "{{SITENAME}}मी खोज",
+       "tooltip-search-go": "यदà¥\80 à¤ à¥\8dयाà¤\95à¥\8dà¤\95à¥\88 à¤¯à¥\87à¤\87 à¤¨à¤¾à¤\89à¤\81 à¤­à¤¯à¤¾: à¤ªà¤¨à¥\8dना à¤°à¥\88à¤\9b à¤­à¤\81णà¥\8dया à¤¤à¥\88 à¤®à¥\80 à¤\9cा:।",
+       "tooltip-search-fulltext": "यà¥\88 à¤ªà¤¾à¤ à¤\95ा à¤²à¤¾à¤\97ि à¤ªà¤¨à¥\8dनाà¤\85नमी खोज",
+       "tooltip-p-logo": "मà¥\81à¤\96à¥\8dय à¤ªà¤¨à¥\8dनामà¥\80 à¤¹à¥\87रऽ",
        "tooltip-n-mainpage": "खास पानामी झान्या",
-       "tooltip-n-mainpage-description": "à¤\96ास à¤ªà¤¾à¤¨à¤¾à¤®à¥\80 à¤\9dा",
+       "tooltip-n-mainpage-description": "à¤\96ास à¤ªà¤¨à¥\8dनामà¥\80 à¤\9dाऽ",
        "tooltip-n-portal": "आयोजनाका बारेमी , तम कि अद्द सकन्छौ , समान काखाइ  भेटौन्या",
        "tooltip-n-currentevents": "हालैका घटनाको बारेमी पृष्ठभूमि जानकारी पत्ता लागाइदिय",
-       "tooltip-n-recentchanges": "विà¤\95िमा à¤\85रियाà¤\95ा à¤¹à¤¾à¤²à¥\88à¤\95ा à¤­à¤¯à¤¾ à¤«à¥\87रबदलà¤\95ा शुचि ।",
-       "tooltip-n-randompage": "à¤\9cà¥\8b à¤\95à¥\8bà¤\87 à¤ªà¤¾à¤¨à¥\8b à¤\96à¥\8bलà¥\8dया",
-       "tooltip-n-help": "खोज्जु पड्या ठौर ।",
-       "tooltip-t-whatlinkshere": "यà¥\8b à¤¸à¤¿à¤¤ à¤\9cà¥\8bडियाà¤\95ा à¤¸à¤¬à¥\8dबà¥\88 à¤µà¤¿à¤\95ि à¤ªà¤¾à¤¨à¤¾à¤¨à¤\95à¥\8b à¤¸à¥\82à¤\9aà¥\80",
+       "tooltip-n-recentchanges": "विà¤\95िमà¥\80 à¤¹à¤¾à¤²à¥\88 à¤\85रियाà¤\95ा à¤«à¥\87रबदलà¥\88 शुचि ।",
+       "tooltip-n-randompage": "à¤\95à¥\8dरमरहित à¤ªà¤¨à¥\8dना à¤\96à¥\8bलऽ",
+       "tooltip-n-help": "à¤\96à¥\8bà¤\9cà¥\8dà¤\9cà¥\81 à¤ªà¤¡à¥\8dडà¥\8dया à¤ à¥\8cर à¥¤",
+       "tooltip-t-whatlinkshere": "सपà¥\8dपà¥\88 à¤µà¤¿à¤\95ि à¤ªà¤¨à¥\8dनाà¤\85नà¥\88 à¤¶à¥\81à¤\9aि à¤\9cà¥\8b à¤¯à¤¾à¤\81à¤\96ाà¤\87 à¤\9cà¥\8bणà¥\80à¤\9cान",
        "tooltip-t-recentchangeslinked": "यै पानामी जोडियाका पानामी अहिलको परिवर्तन",
        "tooltip-feed-atom": "यै पानाकी लेखा एक एटम फिड",
-       "tooltip-t-contributions": "{{GENDER:$1|यिन à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता}}à¤\95ा à¤¯à¥\8bà¤\97दानहरà¥\82à¤\95à¥\8b à¤¸à¥\82à¤\9aà¥\80 à¤¹à¥\87रपà¥\81à¤\88",
-       "tooltip-t-upload": "à¤\9aितà¥\8dर à¤\85पà¥\8dलà¥\8bड à¤\85र",
-       "tooltip-t-specialpages": "सब्बै खास खास पानानको शुचि ।",
-       "tooltip-t-print": "यà¥\8b à¤ªà¤¾à¤¨à¤¾à¤\95à¥\8b à¤\9bापिन्या संस्करण",
+       "tooltip-t-contributions": "{{GENDER:$1|यिन à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता}}ऽ à¤¯à¥\8bà¤\97दानन à¤\90 à¤¶à¥\81à¤\9aि",
+       "tooltip-t-upload": "फाà¤\87ल à¤\85पà¥\8dलà¥\8bड à¤\85रऽ",
+       "tooltip-t-specialpages": "सब्बै खास-खास पन्नाअनै सूची",
+       "tooltip-t-print": "यà¥\87à¤\87 à¤ªà¤¨à¥\8dनाऽ à¤\9bापà¥\8dद à¤®à¤¿à¤²à¥\8dल्या संस्करण",
        "tooltip-t-permalink": "पृष्ठको यो पुनरावलोकनकि लेखा स्थाई लिङ्क",
        "tooltip-ca-nstab-main": "सामाग्री पानो हेरिदिय",
        "tooltip-ca-nstab-user": "प्रयोगकर्ता पानो हेरिदिय",
        "siteusers": "{{SITENAME}} {{PLURAL:$2|प्रयोगकर्ता|प्रयोगकर्ताहरू}} $1",
        "anonusers": "{{SITENAME}} का नाम नभयाका {{PLURAL:$2| प्रयोगकर्ता|प्रयोगकर्ताहरू}} $1",
        "simpleantispam-label": "ऐन्टी-स्प्याम जाँच।\nयैलाई <strong>नाइँ</strong> भद्य्या!",
-       "pageinfo-toolboxlink": "यà¥\88 à¤ªà¤¾à¤¨à¤¾à¤\95à¥\8b à¤\9cाणकारी",
+       "pageinfo-toolboxlink": "पनà¥\8dनाà¤\87 à¤\9cानकारी",
        "rcpatroldisabled": "अहिलका परिवर्तनहरू गस्ती निष्क्रिय पार्याको छ ।",
        "rcpatroldisabledtext": "अहिलका परिवर्तनहरू गस्ती गुण अहिलको लागि निष्कृय पारियाको छ ।",
        "markedaspatrollederror-noautopatrol": "तमी आफ्नै सम्पादनलाई गस्ती गरियाको भनि चिनो लगाउन नाइसक्दा ।",
        "watchlistedit-clear-done": "तमरो ध्यान सूची खाली गरीयाको छ।",
        "watchlisttools-view": "आधारित फेरबदलीहरू हेर",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|कुरडी]])",
-       "specialpages": "à¤\96ास à¤ªà¤¾à¤¨à¥\8b",
+       "specialpages": "à¤\96ास à¤ªà¤¨à¥\8dनाà¤\85न",
        "specialpages-note": "* साधारण खास पानाहरू।\n* <span class=\"mw-specialpagerestricted\">निषेधित खास पानाहरू।</span>",
        "specialpages-group-changes": "अल्लैका परिवर्तन लगहरू",
        "tags": "मान्य परिवर्तन ट्यागहरू",
        "logentry-newusers-create": "प्रयोगकर्ता खाता $1 {{GENDER:$2|खोलियो}}",
        "logentry-upload-upload": "$1 ले $3 {{GENDER:$2|अपलोड अरेका छन्}}",
        "feedback-bugornote": "यदि तमी कुनै प्राविधिक समस्यालाई विस्तारले सम्झाउन तयार छौ भण्या कृपया [$1 बग राख]।\nयदि हैन, भण्या तमी तल दियाको सरल फारमको प्रयोग गद्दसक्द्याहौ । तमरो टिप्पणी, तमरो प्रयोगकर्ता नाम र तमरो ब्राउजरको नाम सहित \"[$3 $2]\" पानामी जोडिन्याछ ।",
-       "searchsuggest-search": "खोज",
+       "searchsuggest-search": "खोज:",
        "api-error-duplicate": "यै साइटमी पहिलीबठे यस्तै सामग्री {{PLURAL:$1|भयाको अर्को फाइल छ|भयाका  केहि अरु फाइलहरू छन्}} ।",
        "api-error-duplicate-archive": "यै साइटमी पहिलेबाट यस्तै सामग्री {{PLURAL:$1|भयाको अर्को फाइल थियो|भयाका केहि अरु फाइलहरू थिए}} ।\nतर {{PLURAL:$1|यो मेट्याको थियो|यी मेटायाका थिए}} ।",
        "expand_templates_preview_fail_html": "<em>किनकि {{SITENAME}} सिधै एचटिएमयल सक्षम छ र तमीले लग इन गर्या छैनौ, पूर्वावलोकन लुकाइयाको छ ताकि सम्भावित जाभास्क्रिप्ट आक्रमणलाई रोक्द सकियोस् ।</em>\n\n<strong>यदि यो मान्य पूर्ववावलोकन प्रयास हो भण्या पुन प्रयास गर ।</strong>\nयदि यसले कार्य पूर्ण भएन भण्या [[Special:UserLogout|लग आउट गरिबर]] फेरी लग इन गर्या ।",
index 7a99fe3..d1d84b0 100644 (file)
        "yourpasswordagain": "Scrév incòra la cêva 'd ingrès:",
        "createacct-yourpasswordagain": "Cunfērma la cêva 'd ingrès",
        "createacct-yourpasswordagain-ph": "Tōrna mèter dèinter la cêva 'd ingrès",
-       "remembermypassword": "Tîn a mèint la cêva 'd ingrès insém a cól navigadōr ché (per un mâsim ed $1{{PLURAL:$1|dé}}).",
        "userlogin-remembermypassword": "Sèimper coleghê",
        "userlogin-signwithsecure": "Drōva un colegamèint sicûr",
        "yourdomainname": "Precişêr al duméni:",
        "noemail": "Nisûn indirés ed pôsta eletrônica registrê per l'utèint $1.",
        "noemailcreate": "L'é necesâri dêr un 'indirés ed pôsta eletrônica vâlid.",
        "passwordsent": "'Na nōva cêva 'd ingrès l'é stêda spidîda a l'indiré ed pôsta eletrônica per l'utèint \"$1\". Per piaşèir, fà un ingrès apèina 't la ricēv.",
-       "blocked-mailpassword": "Per pervèder abûş, an n'é mìa permès druvêr la funsiòun \"spidés 'na nōva cêva 'd ingrès\" da un indirés IP bluchê.",
+       "blocked-mailpassword": "Al tó indirés IP l’è bluchê int la mudéfica. Per pervèder abûş, an n'é mìa permès druvêr la funsiòun ed recóper ed la cêva ‘d ingrès da cl’indirés IP ché.",
        "eauthentsent": "Un mesâg ed cunfèirma l'é stê spidî a l'indirés ed pôsta eletrônica sgnê ché. L'utèint per prèir inviêr di mesâg ed pôsta eletrônica al dēv andêr a drē al j istrusiòun scréti, in môd da cunfermêr ch' l'é ló al legétim proprietâri 'd l'indirés.",
        "throttled-mailpassword": "Un mesâg ed pôsta eletrônica 'd arnōv ed la cêva 'd ingrès l'é bèle stê inviê da mēno 'd {{PLURAL:$1|1 ōra|$1 ōri}}. Per pervèder abûş, la funziòun 'd arnōv ed la cêva 'd ingrès la pōl èser druvêda sōl 'na vôlta ògni {{PLURAL:$1|1 ōra|$1 ōri}}.",
        "mailerror": "Erōr int la spedisiòun dal mesâg $1",
        "createaccount-title": "Per fêr un inscrisiòun a {{SITENAME}}",
        "createaccount-text": "Quelchidûn l'à fât un inscrisiòun a  {{SITENAME}} ($4) a nòm ed $2 coleghê a cl'indirés ed pôsta eletrônica ché. La cêva 'd ingrès per l'utèint \"$2\" l'é  impustêda a \"$3\". \nÉ necesâri fêr un ingrès préma ch' es pôl e cambiêr subét la cêva 'd ingrès. \nSe l'inscrisiòun l'é stêda fâta per şbâli, es pōl scanşlêr sté mesâg.",
        "login-throttled": "În stê fât trôp tentatîv 'd ingrès in pôch tèimp. Spèta $1 e pó tōrna pruvêr.",
-       "login-abort-generic": "An t'é mìa stê arcgnusû - Scanşlê",
+       "login-abort-generic": "Al tó ingrès an n’è mia stê fât – Al vîn scanşlê",
        "login-migrated-generic": "La tó utèinsa l'é stêda spustêda, e al tó nòm utèint al gh'é mìa pió in sém a cla wiki ché.",
        "loginlanguagelabel": "Léngva: $1",
        "suspicious-userlogout": "La tó dmânda per destachêret l'é stēda rifiutêda perchè la sèmbra spidîda da un navigadōr ch' al funsiòuna mìa o da un proxy di caching.",
        "newpassword": "Nōva cêva 'd ingrès:",
        "retypenew": "Scrév incòra la nōva cêva 'd ingrès:",
        "resetpass_submit": "Scrév la cêva 'd ingrès e và dèinter al sît",
-       "changepassword-success": "La cêva 'd ingrès l'é stêda nudifichêda!",
+       "changepassword-success": "La tó cêva 'd ingrès l'é stêda mudifichêda!",
        "changepassword-throttled": "În stê fât trôp tentatîv 'd ingrès in pôch tèimp. Spèta $1 e pó tōrna pruvêr.",
        "resetpass_forbidden": "An 'né mìa pusébil mudifichêr la cêva 'd ingrès",
        "resetpass-no-info": "Per andêr dèinter a cla pàgina ché 't gh'ê da fêr l'ingrès.",
        "resetpass-submit-loggedin": "Câmbia la cêva 'd ingrès",
        "resetpass-submit-cancel": "Scanşèla",
-       "resetpass-wrong-oldpass": "Cêva 'd ingrès pruvişôria o còla 'd adès mìa vâlida.\nLa cêva 'd ingrès la pré èser stêda bèle cambiêda, opór n'in pré èser stê dmandê 'na nōva pruvişôria.",
+       "resetpass-wrong-oldpass": "Cêva 'd ingrès pruvişôria o còla 'd adès an n'é mìa vâlida.\nLa cêva 'd ingrès la pré èser stêda bèle cambiêda, opór in pré èser stê dmandê 'na nōva pruvişôria.",
        "resetpass-recycled": "Mèt dèinter 'na cêva 'd ingrès divêrsa da còla 'd adès.",
        "resetpass-temp-emailed": "L'ingrès l'é stê fât cun un côdis pruvişôri. Per finîr la registrasiòun, l'é necesâri impustêr 'na nōva cêva 'd ingrès ché:",
        "resetpass-temp-password": "Cêva 'd ingrès pruvişôria:",
        "passwordreset-emailtext-ip": "Quelchidûn (prubabilmèint té, cun l'indirés IP $1) l'à dmandê de spidîregh 'na nōva cêva 'd ingrès per andêr dèinter a {{SITENAME}} ($4). {{PLURAL:$3|L'utèint inscrét| J utèint inscrét}} a sté indirés ed pôsta eletrônica în:\n \n$2 \n\n{{PLURAL:$3|Cla cêva 'd ingrès pruvişôria la scadrà| St' al cêvi 'd ingrès pruvişôri ché scadrân}} dôp {{PLURAL:$5|ûn dé|$5 dé}}. Ét duvrés andêr dèinter e sernîr 'na cêva 'd ingrès nōva adès. \n\nSe t'é mìa stê té a fêr la dmânda, o s' ét t'é ricurdê la cêva 'd ingrès uriginêla e an 't vō mia pió cambiêrla, ét pō scanşlêr cól mesâg ché e cuntinvêr a druvêr la tó cêva 'd ingrès vècia.",
        "passwordreset-emailtext-user": "L'utèint $1 ed {{SITENAME}} l'à dmandê de spidîregh 'na nōva cêva 'd ingrès per andêr dèinter a {{SITENAME}} ($4). {{PLURAL:$3|L'utèint inscrét| J utèint inscrét}} a sté indirés ed pôsta eletrônica în:\n\n$2 \n\n{{PLURAL:$3|Cla cêva 'd ingrès pruvişôria ché la scadrà| St' al cêvi 'd ingrès pruvişôri ché scadrân}} dôp {{PLURAL:$5|ûn dé|$5 dé}}. Ét duvrés andêr dèinter e sernîr 'na cêva 'd ingrès nōva adès. \n\nSe t'é mìa stê té a fêr la dmânda, o s' ét t'é ricurdê la cêva 'd ingrès uriginêla e an 't vō mia pió cambiêrla, ét pō scanşlêr cól mesâg ché e cuntinvêr a druvêr la tó cêva 'd ingrès vècia",
        "passwordreset-emailelement": "Nòm utèint: \n$1\n.\nCêva 'd ingrès pruvişôria: \n$2",
-       "passwordreset-emailsentemail": "É stê spidî un mesâg ed pôsta eletrônica per turnêr a impustêr la cêva 'd ingrès.",
-       "changeemail": "Câmbia l'indirés ed la pôsta eletrônica",
-       "changeemail-header": "Câmbia l'indirés ed la pôsta eletrônica 'd la tó inscrisiòun.",
+       "passwordreset-emailsentemail": "Se cl’indirés ed pôsta eletrônica ché l’è unî a la tó utèinsa, alōra a gnirà spidî ‘na lètra per per turnêr a impustêr la cêva ‘d ingrès.",
+       "changeemail": "Mudéfica o tó via l'indirés ed pôsta eletrônica",
+       "changeemail-header": "Finés cól fòj ché per cambiêr al tó indirés ed pôsta eletrônica, 'S an 't vō mia avèir nisûn indirés coleghê a la tó utèinsa lêsa vōd al spâsi per l'indirés nōv quând té spidés al fòj.",
        "changeemail-no-info": "Per andêr dèinter diretamèint a cla pàgina ché 't gh'ê da fêr l'ingrès.",
        "changeemail-oldemail": "L'indirés ed la pôsta eletrànica 'd adès.",
        "changeemail-newemail": "Nōv indirés ed pàsta eletrônica:",
        "sig_tip": "Fîrma cun la dâta e l'ōra",
        "hr_tip": "Rîga spiâna (drōva cun giudési)",
        "summary": "Ogèt:",
-       "subject": "Argumèint (tétol):",
+       "subject": "Argumèint:",
        "minoredit": "Còsta l'é 'na mudéfica céca",
        "watchthis": "Tîn adrē a cla pàgina ché",
        "savearticle": "Sêlva la pàgina",
        "missingsummary": "'''Atensiòun:''' an n'é mìa stê precişê al mutîv de sté mudéfica. S'es tōrna a clichêr insém a \"{{int:savearticle}}\" la mudéfica la gnirà salvêda cun al mutîv vōd.",
        "selfredirect": "<strong>Ateinti:</strong>t'é drē fêr un rinvéi a l'istèsa pàgina. Ét prés avèir şbaliê sgnêr al pôst dal rinvéi o t'é drē mudifichêr la pàgina şbaliêda. S'ét fê cléch incòra in sém a \"{{int:savearticle}}\", al rinvéi al gnirà fât in tót' al manēri.",
        "missingcommenttext": "Scréver un cumèint ché sòta.",
-       "missingcommentheader": "'''Atensiòun:''' an n'é mìa stê precişê al mutîv/al tétol de sté mudéfica. S'es tōrna a clichêr insém a \"{{int:savearticle}}\" la mudéfica la gnirà salvêda sèinsa tétol.",
+       "missingcommentheader": "<strong>Atensiòun:<strong> an n'é mìa stê precişê l'argumèint de sté mudéfica. S'es tōrna a clichêr insém a \"{{int:savearticle}}\" la mudéfica la gnirà salvêda sèinsa.",
        "summary-preview": "Guêrda préma sûnt:",
-       "subject-preview": "Guêrda préma argumèint/tétol:",
+       "subject-preview": "Guêrda préma l'argumèint:",
        "previewerrortext": "A gh'é stê 'n erōr mèinter a s'é serchê ed guardêr al lavōr préma 'd salvêrel.",
        "blockedtitle": "Utèint bluchê",
        "blockedtext": " '''Al tō nòm utèint o indirés IP l'é stê bluchê.'''\n\nAl blôch l'é stê fât da $1. Al mutîv dal blôch l'é còst:  ''$2''.\n\n*Inési dal blôch: $8\n*Scadèinsa dal blôch: $6\n*Intervâl ed blôch: $7\n\nS' ét vō, l'é pusébil mètres in cuntât cun $1 o 'n êter [[{{MediaWiki:Grouppage-sysop}}|aministradōr]] per discóter dal blôch.\n\nGuêrda che la funsiòun 'Scrév a l'utèint' an n'é mìa in ôvra s' an n'é mìa stê registrtê un indirés ed pôsta eletrônica vâlid int al tō [[Special:Preferences| preferèinsi]] o se sté funsiòun l'é stêda bluchêda. L'indirés IP 'd adèsa l'é $3, al nóme ID dal blôch l'é #$5. T'é perghê ed precişêr tót j elemèint ed préma per ògni dmânda de spiegasiòun",
        "accmailtext": "'Na cêva 'd ingrés l'è stêda fâta a chêş per [[User talk:$1|$1]] e l'è stêda spidîda a $2. Cla cêva 'd ingrès ché la pōl èser cambiêda int la pàgina per ''[[Special:ChangePassword|cambiêr la cêva 'd ingrès]]'' subét dôp avèir fât l'ingrès.",
        "newarticle": "(Nōv)",
        "newarticletext": "Al colegamèint apèina fât al cumbîna cun 'na pàgina ch' an n'é mìa incòra stêda fâta. S'ét vō fêr la pàgina adès, l'é asê cumincêr a scréver al tèst int la caşèla ché sòt (per vedèr infurmasiòun pió precîşi guêrda la [$1 pàgina 'd ajót]). Se al colegamèint  l'é stê avêrt per erōr, l'é asê clichêr al pulsânt \"Indrē\" dal tó navigadōr.",
-       "anontalkpagetext": "----'' Còsta l'è la pàgina 'd discusiòun ed 'n utèint sèinsa nòm, ch' an n' à mìa incòra fât 'n' utèinsa o in tót al manēri an n'è mìa drē druvêrla. Per arcgnòsrel l'è dòunca necesâri druvê al só indirés IP. J indirés IP a pōlen èser spartî cun êter utèint. Se t'è un utèint sèinsa nòm e 't pèins che i cumèint in cla pàgina ché an riguêrden mìa tè, [[Special:CreateAccount|fa 'n' utèinsa nōva]] o [[Special:UserLogin|vîn dèinter cun còla ch' ét gh'ê bèle]] per schivşêr, in futûr,  'd èser cunfûş cun 'd j êter utèint sèinsa nòm.''",
-       "noarticletext": "In cól mumèint ché la pàgina serchêda l'é vōda. L'é pusébil [[Special:Search/{{PAGENAME}}|serchêr sté tétol]] int al j êtri pàgini dal sît, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serchêr int i regéster coleghê] opór  [{{fullurl:{{FULLPAGENAME}}|action=edit}} mudifichêr la pàgina adèsa]</span>.",
+       "anontalkpagetext": "----\n<em>Còsta l'è la pàgina 'd discusiòun ed 'n utèint sèinsa nòm, ch' an n' à mìa incòra fât 'n' utèinsa o in tót al manēri an n'è mìa drē druvêrla.</em> Per arcgnòsrel l'è dòunca necesâri druvê al nóme dal só indirés IP. J indirés IP a pōlen èser spartî cun êter utèint. Se t'é un utèint sèinsa nòm e 't pèins che i cumèint in cla pàgina ché an riguêrden mìa té, [[Special:CreateAccount|fa 'n' utèinsa nōva]] o [[Special:UserLogin|vîn dèinter cun còla ch' ét gh'ê bèle]] per schivşêr, in futûr,  'd èser cunfûş cun 'd j êter utèint sèinsa nòm.",
+       "noarticletext": "In cól mumèint ché la pàgina serchêda l'é vōda.Ét pō\n[[Special:Search/{{PAGENAME}}|serchêr cól tétoi ché]] int al j êtri pàgini dal sît, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serchêr int i regéster coleghê] opór  [{{fullurl:{{FULLPAGENAME}}|action=edit}} e fêr cla pàgina ché]</span>.",
        "noarticletext-nopermission": "In cól mumèint ché la pàgina serchêda l'é vōda. L'é pusébil [[Special:Search/{{PAGENAME}}|serchêr sté tétol]] int al j êtri pàgini dal sît o<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serchêr int i regéster coleghê] <span>, mó an 't gh'ê mìa al permès ed fêr cla pàgina ché.",
        "missing-revision": "La revişiòun #$1 'd la pagina \"{{FULLPAGENAME}}\" l' an gh'è mìa. Còst, ed sôlit, a sucēd mèint'r as va drē a 'n colegamèint a 'na pàgina scanşlêda, in 'na stòria, di lavōr fât, mìa arnuvêda. I particulêr a 's pōlen catêr int al [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} regéster dal scanşladûri].",
        "userpage-userdoesnotexist": "L'inscrisiòun \"<nowiki>$1</nowiki>\" la cumbîna mìa cun 'n utèint registrê. Ét sicûr ed vrèir fêr o mudifichêr cla pàgina ché.",
        "userpage-userdoesnotexist-view": "L'utèin \"$1\" an n'à mìa fât l'inscrisiòun.",
        "blocked-notice-logextract": "Cl'utèint ché adèsa l'é bluchê. \nPer infurmasiòun l'ûltem elemèint dal regéster di blôch l'é scrét ché sòta:",
-       "clearyourcache": "'''Nôta:''' dôpa vèir salvê a pré èser necesâri pulîr la memôria pruvişôria dal navigadôr per vèder i cambiamèint.\n*'''Firefox / Safari''': tgnîr cucê al tâst dal lètri grândi e clichêr insém a \"Ricarica\" opór cucêr i tâst ''Ctrl-F5'' o ''Ctrl-R'' (''⌘-R'' insém a un Mac)\n*'''Google Chrome''': cucêr i tâst ''Ctrl-Shift-R'' (''⌘-Shift-R'' insém a un Mac) \n*'''Internet Explorer''': tgnîr cucê al tâst ''Ctrl'' mènter es fà cléch insém a ''Refresh'', opór cucêr ''Ctrl-F5'' \n*'''Opera''': svudêr dal tót la memôria pruvişôria 'd la lésta ''Strumenti → Preferenze''",
+       "clearyourcache": "<strong>Nôta:</strong> dôpa vèir salvê a pré èser necesâri pulîr la memôria pruvişôria dal navigadôr per vèder i cambiamèint.\n*< strong >Firefox / Safari /<strong>: tgnîr cucê al tâst dal lètri grândi <em>Shift</em> e clichêr in sém a <em>Ricarica</em>, opór cucêr i tâst <em>Ctrl-F5</em> o <em>Ctrl-R</em> (<em>⌘-R</em> in sém a ‘n Mac)\n*<strong>Google Chrome:</strong>: cucêr i tâst <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> in sém a ‘n Mac) \n*<strong>Internet Explorer:</strong>: tgnîr cucê al tâst<em>Ctrl</em> e fêr cléch in sém a em>Aggiorna</em>, opór clichêr <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Và int al <em>Menu → Impostazioni</em> (<em>Opera → Preferenze</em> in séma 'n Mac)  e pó in <em>Privacy & sicurezza → Pulisci dati del browser → Immagini e file nella cache</em>.",
        "usercssyoucanpreview": "'''Cunséli:''' drōva al tâst 'Guêrda préma' per pruvêr al tó nōv CSS préma 'd salvêrel'''",
        "userjsyoucanpreview": "'''Cunséli:''' drōva al tâst 'Guêrda préma' per pruvêr al tó nōv  JavaScript préma 'd salvêrel'''",
        "usercsspreview": "'''Còsta l'é sōl 'na guardêda al tó CSS préma 'd salvêr al mudéfichi ch'în stêdi fâti.Ricôrdet che al mudéfichi în mìa incòra stêdi salvêdi!'''",
        "previewnote": "'''Ricôrdet che còsta l'é sōl 'na guardêda préma 'd salvêr.'''\nAl tō mudéfichi în MIA incòra stêdi salvêdi.",
        "continue-editing": "Và int la zôna 'd mudéfica",
        "previewconflict": "La vésta la cumbîna cun al tèst int la zôna 'd mudéfica tèst ché d'ed sōver e l'é cme la srà la pàgina s'ed decéd ed clichêr insém a \"Sêlva la pàgina\" in cól mumèint ché.",
-       "session_fail_preview": "'''An n'é mìa stê pusébil registrêr la mudéfica perchè a s' în pêrsi al j infurmasiòun relatîvi a la sesiòun. Tōrna a pruvêr. Se al prublēma al cunténva, a 's pōl pruvêr [[Special:UserLogout|ed coleghêres]] e fêr un ingrès nōv.'''",
+       "session_fail_preview": "A's în dispiêş. An n'é mìa stê pusébil registrêr la mudéfica perchè a 's în pêrsi al j infurmasiòun relatîvi a la sesiòun. Ét prés èser stê destachê. <strong>Contròla s' t'é incòra coleghê</strong>. Se al problēma 'l cunténva, a 's pōl pruvêr [[Special:UserLogout|ed coleghêres]] e fêr un ingrès nōv, contròla ânch se al tó navigadōr l' acèta i cookie da cól sît ché.",
        "session_fail_preview_html": "'''An n'é mìa stê pusébil registrêr la mudéfica perchè în andêdi persi al j infurmasiòun relatîvi a la sesiòun.'''\n\n''Pôst che in {{SITENAME}} a gh'é al permès ed druvêr l' HTML sèinsa lémit, an 's pōl mìa guardêr préma la pàgina mudifichêda; a 's trâta ed 'n'amzûra 'd sicurèsa cûntra j atâch JavaScript.''\n\n''' Se còst l'é un tentatîv legétim ed mudéfica, pruvêr incòra. Se al prublēma l'armâgn, a 's pōl pruvêr a [[Special:UserLogout|sarêr al colegamèint]] e fêr un nōv ingrès.'''",
        "token_suffix_mismatch": "'''La mudéfica an n'é mìa stêda salvêda perchè al ''client'' l'à fât vèder ed gestîr in môd e-sbaliê i carâter di pûn e dal virgûli int al ''token'' lighê a la mudéfica. Per schivşêr di pusébil erōr int al tèst ed la pàgina, è stê rifiutê tóta la mudéfica. Dla vôlti cla situasiòun ché la pōl sucēder quând a vînen druvê soquânt servési ''proxy'' sèinsa nòm via internèt che preşèinten di ''bug''.'''",
        "edit_form_incomplete": "'''Soquânti pêrt dal môdul ed mudéfica în mìa rivêdi al ''server''; controlêr che al mudéfichi sién intâti e turnêr a pruvêr'''",
index 12f6e28..a684a3f 100644 (file)
        "yourpasswordagain": "Επαναπληκτρολόγηση κωδικού:",
        "createacct-yourpasswordagain": "Επιβεβαίωση κωδικού",
        "createacct-yourpasswordagain-ph": "Εισαγωγή κωδικού ξανά",
-       "remembermypassword": "Απομνημόνευση της σύνδεσής μου σε αυτόν τον περιηγητή (για μέγιστο $1 {{PLURAL:$1|ημέρα|ημέρες}})",
        "userlogin-remembermypassword": "Να διατηρούμαι μόνιμα σε σύνδεση",
        "userlogin-signwithsecure": "Χρησιμοποιείστε ασφαλή σύνδεση",
+       "cannotlogin-title": "Δεν μπορώ να συνδεθώ",
+       "cannotlogin-text": "Η σύνδεση δεν είναι δυνατή.",
        "cannotloginnow-title": "Δεν μπορείτε να συνδεθείτε τώρα",
        "cannotloginnow-text": "Η σύνδεση δεν είναι δυνατή όταν χρησιμοποιείτε την $1.",
        "yourdomainname": "Το domain σας:",
        "previewnote": "'''Να θυμάστε ότι αυτή είναι μόνο μια προεπισκόπηση.'''\nΟι αλλαγές σας δεν έχουν ακόμη αποθηκευτεί!",
        "continue-editing": "Μεταβείτε στην περιοχή επεξεργασίας",
        "previewconflict": "Αυτή η προεπισκόπηση απεικονίζει το κείμενο στην επάνω περιοχή επεξεργασίας κειμένου, όπως θα εμφανιστεί εάν επιλέξετε να το αποθηκεύσετε.",
-       "session_fail_preview": "'''Συγγνώμη! Δεν μπορούσαμε να διεκπεραιώσουμε την επεξεργασία σας λόγω απώλειας των δεδομένων της συνεδρίας.\nΠαρακαλώ προσπαθήστε ξανά. Αν δεν δουλεύει ξανά, δοκιμάστε να αποσυνδεθείτε και να συνδεθείτε πάλι.'''",
+       "session_fail_preview": "'''Συγγνώμη! Δεν μπορούσαμε να διεκπεραιώσουμε την επεξεργασία σας λόγω απώλειας των δεδομένων της συνεδρίας.\nΠαρακαλώ προσπαθήστε ξανά. Αν δεν δουλεύει ξανά, δοκιμάστε να [[Special:UserLogout|αποσυνδεθείτε]] και να συνδεθείτε πάλι.'''",
        "session_fail_preview_html": "'''Λυπούμαστε! Δεν μπορέσαμε να διεκπεραιώσουμε την επεξεργασία σας λόγω απώλειας των δεδομένων της συνεδρίας.'''\n\n''Επειδή το {{SITENAME}} επιτρέπει την εισαγωγή ακατέργαστου HTML, η προεπισκόπηση είναι κρυμμένη ως προφύλαξη ενάντια σε επιθέσεις με Javascript.''\n\n'''Αν αυτή είναι μια έγκυρη προσπάθεια επεξεργασίας, παρακαλώ προσπαθήστε ξανά. Αν πάλι δε δουλεύει, δοκιμάστε να αποσυνδεθείτε και να συνδεθείτε πάλι.'''",
        "token_suffix_mismatch": "'''Η επεξεργασία σας απορρίφθηκε γιατί το πρόγραμμα-πελάτη σας κατακρεούργησε τους χαρακτήρες στίξης στο κουπόνι επεξεργασίας. Η επεξεργασία απορρίφθηκε για να αποφευχθεί η παραφθορά του κειμένου της σελίδας.\nΑυτό μερικές φορές συμβαίνει όταν χρησιμοποιείται ένας ανώνυμος διακομιστής μεσολάβησης διαθέσιμος μέσω του παγκόσμιου ιστού με σφάλματα.'''",
        "edit_form_incomplete": "'''Ορισμένα τμήματα της φόρμας επεξεργασίας δεν έφθασαν στο διακομιστή. Ελέγξτε ότι οι αλλαγές σας είναι άθικτες και προσπαθήστε ξανά.'''",
        "mergehistory-fail-bad-timestamp": "Η χρονική σήμανση δεν είναι έγκυρη.",
        "mergehistory-fail-invalid-source": "Η πηγή σελίδας δεν είναι έγκυρη.",
        "mergehistory-fail-invalid-dest": "Η σελίδα προορισμού δεν είναι έγκυρη.",
+       "mergehistory-fail-permission": "Μη επαρκή δικαιώματα για τη συγχώνευση του ιστορικού.",
+       "mergehistory-fail-self-merge": "Η πηγή και ο προορισμός των σελίδων είναι ο ίδιος.",
        "mergehistory-fail-toobig": "Δεν είναι δυνατό να πραγματοποιηθεί η συγχώνευση ιστορικών, καθώς πάνω από $1 {{PLURAL:$1|αναθεώρηση|αναθεωρήσεις}} θα μετακινούνταν.",
        "mergehistory-no-source": "Η σελίδα πηγής $1 δεν υπάρχει.",
        "mergehistory-no-destination": "Η σελίδα προορισμού $1 δεν υπάρχει.",
        "grant-group-high-volume": "Εκτέλεση υψηλής έντασης δραστηριότητας",
        "grant-group-customization": "Ρυθμίσεις και προτιμήσεις",
        "grant-group-administration": "Εκτέλεση διαχειριστικών ενεργειών",
+       "grant-group-private-information": "Πρόσβαση σε ιδιωτικά δεδομένα σχετικά με εσάς",
+       "grant-group-other": "Διάφορες δραστηριότητες",
        "grant-blockusers": "Φραγή και αναίρεση φραγής χρηστών",
        "grant-createaccount": "Δημιουργία λογαριασμών",
        "grant-createeditmovepage": "Δημιουργία, επεξεργασία και μετακίνηση σελίδων",
        "grant-highvolume": "Υψηλής έντασης επεξεργασία",
        "grant-oversight": "Απόκρυψη χρηστών και καταστολή αναθεωρήσεων",
        "grant-patrol": "Περιπολία αλλαγών σε σελίδες",
+       "grant-privateinfo": "Πρόσβαση σε προσωπικές πληροφορίες",
        "grant-protect": "Προστασία και κατάργηση προστασίας σελίδων",
        "grant-rollback": "Η επαναφορά αλλαγών σε σελίδες",
        "grant-sendemail": "Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου σε άλλους χρήστες",
        "action-applychangetags": "εφαρμογή ετικετών μαζί με τις αλλαγές σας",
        "action-changetags": "πρόσθεση και αφαίρεση αυθαίρετων ετικετών σε μεμονωμένες εκδόσεις και καταχωρήσεις καταγραφών",
        "action-deletechangetags": "διαγράψετε ετικέτες από τη βάση δεδομένων",
+       "action-purge": "εκκαθάριση αυτής της σελίδας",
        "nchanges": "$1 {{PLURAL:$1|αλλαγή|αλλαγές}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|από την τελευταία επίσκεψη}}",
        "enhancedrc-history": "ιστορικό",
        "uploadstash-summary": "Η σελίδα παρέχει πρόσβαση σε αρχεία που είναι  επιφορτωμένα  (ή στη διαδικασία της επιφόρτωσης) αλλά δεν έχει ακόμη δημοσιευθεί για το wiki. Αυτά τα αρχεία δεν είναι ορατά σε  οποιονδήποτε, αλλά στο χρήστη που τα επιφόρτωσε.",
        "uploadstash-clear": "Καθαρά διατηρημένα αρχεία",
        "uploadstash-nofiles": "Δεν έχετε κρυμμένα αρχεία.",
-       "uploadstash-badtoken": "Î\95κÏ\84έλεÏ\83η Ï\84ηÏ\82 ÎµÎ½ Î»Ï\8cγÏ\89 ÎµÎ½Î­Ï\81γειαÏ\82  Î®Ï\84αν Î±Î½ÎµÏ\80ιÏ\84Ï\85Ï\87ήÏ\82, Î¯Ï\83Ï\89Ï\82 ÎµÏ\80ειδή Ï\84α Î´Î¹Î±Ï\80ιÏ\83Ï\84εÏ\85Ï\84ήÏ\81ιά ÎµÏ\80εξεÏ\81γαÏ\83ίαÏ\82  Ï\83αÏ\82 Î­Ï\87οÏ\85ν Î»Î®Î¾ÎµÎ¹. Î\94οκίμαστε ξανά.",
-       "uploadstash-errclear": "Î\97 ÎµÎºÎºÎ±Î¸Î¬Ï\81ιÏ\83η Ï\84Ï\89ν Î±Ï\81Ï\87είÏ\89ν Î®Ï\84αν Î±Î½ÎµÏ\80ιÏ\84Ï\85Ï\87ήÏ\82.",
+       "uploadstash-badtoken": "Î\95κÏ\84έλεÏ\83η Ï\84ηÏ\82 ÎµÎ½ Î»Ï\8cγÏ\89 ÎµÎ½Î­Ï\81γειαÏ\82  Î±Ï\80έÏ\84Ï\85Ï\87ε, Î¯Ï\83Ï\89Ï\82 ÎµÏ\80ειδή Ï\84α Î´Î¹Î±Ï\80ιÏ\83Ï\84εÏ\85Ï\84ήÏ\81ιά ÎµÏ\80εξεÏ\81γαÏ\83ίαÏ\82  Ï\83αÏ\82 Î­Ï\87οÏ\85ν Î»Î®Î¾ÎµÎ¹. Î Î±Ï\81ακαλοÏ\8dμε Î´Î¿ÎºÎ¹Î¼Î¬στε ξανά.",
+       "uploadstash-errclear": "Î\97 ÎµÎºÎºÎ±Î¸Î¬Ï\81ιÏ\83η Ï\84Ï\89ν Î±Ï\81Ï\87είÏ\89ν Î±Ï\80έÏ\84Ï\85Ï\87ε.",
        "uploadstash-refresh": "Ανανεώσετε τη λίστα των αρχείων",
+       "uploadstash-thumbnail": "προβολή μικρογραφίας",
        "invalid-chunk-offset": "Άκυρο κομμάτι όφσετ",
        "img-auth-accessdenied": "Δεν επετράπη η πρόσβαση",
        "img-auth-nopathinfo": "Λείπει το PATH_INFO.\nΟ διακομιστής σας δεν είναι ρυθμισμένος για να περάσει αυτές τις πληροφορίες.\nΜπορεί να είναι βασισμένος σε CGI και να μην υποστηρίζει img_atuh.\nΔείτε https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
        "apisandbox-helpurls": "Σύνδεσμοι βοήθειας",
        "apisandbox-examples": "Παραδείγματα",
        "apisandbox-dynamic-parameters": "Πρόσθετες παράμετροι",
+       "apisandbox-dynamic-parameters-add-label": "Προσθήκη παραμέτρου:",
        "apisandbox-dynamic-parameters-add-placeholder": "Ονομασία παραμέτρου",
        "apisandbox-dynamic-error-exists": "Η παράμετρος με την ονομασία \"$1\" υπάρχει ήδη",
        "apisandbox-submit-invalid-fields-title": "Κάποια από τα πεδία δεν είναι έγκυρα",
        "listgrouprights-namespaceprotection-header": "Περιορισμοί ονοματοχώρων",
        "listgrouprights-namespaceprotection-namespace": "Ονοματοχώρος",
        "listgrouprights-namespaceprotection-restrictedto": "Δικαίωμα(τα) που επιτρέπει(ουν) σε χρήστη να επεξεργαστεί",
+       "listgrants": "Επιχορηγήσεις",
+       "listgrants-grant": "Επιχορήγηση",
        "listgrants-rights": "Δικαιώματα",
        "trackingcategories": "Κατηγορίες παρακολούθησης",
        "trackingcategories-summary": "Αυτή η σελίδα εμφανίζει τις κατηγορίες παρακολούθησης το περιεχόμενο των οποίων συμπληρώνεται αυτόματα από το λογισμικό MediaWiki. Τα ονόματά τους μπορεί να αλλαχθούν με την αλλαγή των σχετικών μηνυμάτων συστήματος στον ονοματοχώρο {{ns:8}}.",
        "delete-toobig": "Αυτή η σελίδα έχει μεγάλο ιστορικό τροποποιήσεων, πάνω από $1 {{PLURAL:$1|τροποποίηση|τροποποιήσεις}}.\nΗ διαγραφή τέτοιων σελίδων έχει περιοριστεί για την αποφυγή τυχαίας αναστάτωσης του {{SITENAME}}.",
        "delete-warning-toobig": "Αυτή η σελίδα έχει μεγάλο ιστορικό τροποποιήσεων, πάνω από $1 {{PLURAL:$1|τροποποίηση|τροποποιήσεις}}.\nΗ διαγραφή της μπορεί να αναστατώσει τη λειτουργία της βάσης δεδομένων του {{SITENAME}}. Συνιστούμε μεγάλη προσοχή.",
        "deleteprotected": "Δεν μπορείτε να διαγράψετε αυτή τη σελίδα επειδή είναι προστατευόμενη.",
-       "deleting-backlinks-warning": "\"'Προσοχή:\"' [[Special:WhatLinksHere/{{FULLPAGENAME}}|Άλλες σελίδες]] συνδέουν ή ενσωματώνουν τη σελίδα που πρόκειται να διαγράψετε.",
+       "deleting-backlinks-warning": "<strong>Προσοχή:</strong>  [[Special:WhatLinksHere/{{FULLPAGENAME}}|Άλλες σελίδες]] συνδέουν ή ενσωματώνουν τη σελίδα που πρόκειται να διαγράψετε.",
        "rollback": "Επαναφορά επεξεργασιών",
        "rollbacklink": "αναστροφή",
        "rollbacklinkcount": "Επαναφορά $1 {{PLURAL:$1|επεξεργασίας|επεξεργασιών}}",
        "undeletedrevisions": "{{PLURAL:$1|τροποποίηση|τροποποιήσεις}} αποκαταστάθηκαν",
        "undeletedrevisions-files": "$1 {{PLURAL:$1|αναθεώρηση|αναθεωρήσεις}} και $2 {{PLURAL:$2|αρχείο|αρχεία}} επαναφέρθηκαν",
        "undeletedfiles": "$1 {{PLURAL:$1|αρχείο|αρχεία}} επαναφέρθηκαν",
-       "cannotundelete": "Î\97 Î±Î½Î±Î¯Ï\81εÏ\83η Î´Î¹Î±Î³Ï\81αÏ\86ήÏ\82 Î±Ï\80έÏ\84Ï\85Ï\87ε: $1",
+       "cannotundelete": "Î\9cεÏ\81ικέÏ\82 Î® Ï\8cλεÏ\82 Î¿Î¹  Î±Î½Î±Î¹Ï\81έÏ\83ειÏ\82 Î´Î¹Î±Î³Ï\81αÏ\86ήÏ\82 Î±Ï\80έÏ\84Ï\85Ï\87αν: $1",
        "undeletedpage": "'''Η $1 έχει επαναφερθεί'''\n\nΣυμβουλευτείτε το [[Special:Log/delete|αρχείο καταγραφής διαγραφών]] για ένα μητρώο των πρόσφατων διαγραφών και επαναφορών.",
        "undelete-header": "Δείτε [[Special:Log/delete|το αρχείο καταγραφής διαγραφών]] για πρόσφατα διαγεγραμμένες σελίδες.",
        "undelete-search-title": "Αναζήτηση διαγεγραμμένων σελίδων",
        "sp-contributions-newbies-sub": "Για νέους λογαριασμούς",
        "sp-contributions-newbies-title": "Συνεισφορές χρηστών για νέους λογαριασμούς",
        "sp-contributions-blocklog": "αρχείο καταγραφών φραγών",
-       "sp-contributions-suppresslog": "διαγεγραμμένες συνεισφορές χρήστη",
+       "sp-contributions-suppresslog": "διαγεγραμμένες συνεισφορές {{GENDER:$1|χρήστη|χρήστριας}}",
        "sp-contributions-deleted": "διαγεγραμμένες συνεισφορές χρήστη",
        "sp-contributions-uploads": "ανεβάσματα αρχείων",
        "sp-contributions-logs": "καταγραφές",
        "watchlistedit-raw-done": "Η λίστα παρακολούθησής σας ενημερώθηκε.",
        "watchlistedit-raw-added": "{{PLURAL:$1|1 σελίδα|$1 σελίδες}} προστέθηκαν:",
        "watchlistedit-raw-removed": "{{PLURAL:$1|1 σελίδα|$1 σελίδες}} αφαιρέθηκαν:",
-       "watchlistedit-clear-title": "Î\95κκαθαÏ\81ιÏ\83μένη Î»Î¯Ï\83Ï\84α παρακολούθησης",
+       "watchlistedit-clear-title": "Î\95κκαθάÏ\81ιÏ\83η Î»Î¯Ï\83Ï\84αÏ\82 παρακολούθησης",
        "watchlistedit-clear-legend": "Εκκαθάριση λίστας παρακολούθησης",
        "watchlistedit-clear-explain": "Όλοι οι τίτλοι θα αφαιρεθούν από τη λίστα παρακολούθησής σας",
        "watchlistedit-clear-titles": "Τίτλοι:",
        "version-libraries-license": "Άδεια χρήσης",
        "version-libraries-description": "Περιγραφή",
        "version-libraries-authors": "Δημιουργοί",
-       "redirect": "Ανακατεύθυνση κατά αρχείο, χρήστη, σελίδα ή αναγνωριστικό αναθεώρησης",
+       "redirect": "Ανακατεύθυνση κατά αρχείο, χρήστη, σελίδα, αναγνωριστικό αναθεώρησης ή αρχείο καταγραφής ID",
        "redirect-submit": "Μετάβαση",
        "redirect-lookup": "Αναζήτηση:",
        "redirect-value": "Τιμή:",
        "tags-delete-submit": "Μη αναστρέψιμη διαγραφή αυτής της ετικέτας",
        "tags-delete-not-found": "Η ετικέτα «$1» δεν υπάρχει.",
        "tags-delete-too-many-uses": "Η ετικέτα «$1» εφαρμόζεται σε πάνω από {{PLURAL:$2|μία αναθεώρηση|$2 αναθεωρήσεις}}, που σημαίνει ότι δεν μπορεί να διαγραφεί.",
-       "tags-delete-warnings-after-delete": "Η ετικέτα «$1» διαγράφηκε με επιτυχία, αλλά {{PLURAL:$2|προέκυψε η ακόλουθη προειδοποίηση|προέκυψαν οι ακόλουθες προειδοποιήσεις}}:",
+       "tags-delete-warnings-after-delete": "Η ετικέτα «$1» διαγράφηκε, αλλά {{PLURAL:$2|προέκυψε η ακόλουθη προειδοποίηση|προέκυψαν οι ακόλουθες προειδοποιήσεις}}:",
        "tags-delete-no-permission": "Δεν έχετε άδεια να διαχειριστείτε ετικέτες αλλαγής.",
        "tags-activate-title": "Ενεργοποίηση ετικέτας",
        "tags-activate-question": "Πρόκειται να ενεργοποιήσετε την ετικέτα «$1».",
        "tags-edit-reason": "Αιτία:",
        "tags-edit-revision-submit": "Εφαρμογή αλλαγών σε {{PLURAL:$1|αυτή την αναθεώρηση|$1 αναθεωρήσεις}}",
        "tags-edit-logentry-submit": "Εφαρμογή αλλαγών σε {{PLURAL:$1|αυτήν την καταχώρηση|$1 καταχωρήσεις}} του αρχείου καταγραφής",
-       "tags-edit-success": "Οι αλλαγές εφαρμόστηκαν με επιτυχία.",
+       "tags-edit-success": "Οι αλλαγές εφαρμόστηκαν.",
        "tags-edit-failure": "Οι αλλαγές δεν ήταν δυνατόν να εφαρμοστούν:\n$1",
        "tags-edit-nooldid-title": "Μη έγκυρη αναθεώρηση προορισμού",
        "tags-edit-none-selected": "Παρακαλώ επιλέξτε τουλάχιστον μία ετικέτα για να προσθέσετε ή να αφαιρέσετε.",
index dcd3981..cc7466b 100644 (file)
        "yourpasswordagain": "Retype password:",
        "createacct-yourpasswordagain": "Confirm password",
        "createacct-yourpasswordagain-ph": "Enter password again",
-       "remembermypassword": "Remember my login on this browser (for a maximum of $1 {{PLURAL:$1|day|days}})",
        "userlogin-remembermypassword": "Keep me logged in",
        "userlogin-signwithsecure": "Use secure connection",
+       "cannotlogin-title": "Cannot log in",
+       "cannotlogin-text": "Logging in is not possible.",
        "cannotloginnow-title": "Cannot log in now",
        "cannotloginnow-text": "Logging in is not possible when using $1.",
+       "cannotcreateaccount-title": "Cannot create accounts",
+       "cannotcreateaccount-text": "Direct account creation is not enabled on this wiki.",
        "yourdomainname": "Your domain:",
        "password-change-forbidden": "You cannot change passwords on this wiki.",
        "externaldberror": "There was either an authentication database error or you are not allowed to update your external account.",
        "file-thumbnail-no": "The filename begins with <strong>$1</strong>.\nIt seems to be an image of reduced size <em>(thumbnail)</em>.\nIf you have this image in full resolution upload this one, otherwise change the filename please.",
        "fileexists-forbidden": "A file with this name already exists, and cannot be overwritten.\nIf you still want to upload your file, please go back and use a new name.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "A file with this name exists already in the shared file repository.\nIf you still want to upload your file, please go back and use a new name.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "The upload is an exact duplicate of the current version of <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "The upload is an exact duplicate of {{PLURAL:$2|an older version|older versions}} of <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "This file is a duplicate of the following {{PLURAL:$1|file|files}}:",
        "file-deleted-duplicate": "A file identical to this file ([[:$1]]) has previously been deleted.\nYou should check that file's deletion history before proceeding to re-upload it.",
        "file-deleted-duplicate-notitle": "A file identical to this file has previously been deleted, and the title has been suppressed.\nYou should ask someone with the ability to view suppressed file data to review the situation before proceeding to re-upload it.",
        "filerevert-submit": "Revert",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong> has been reverted to the [$4 version as of $3, $2].",
        "filerevert-badversion": "There is no previous local version of this file with the provided timestamp.",
+       "filerevert-identical": "The current version of the file is already identical to the selected one.",
        "filedelete": "Delete $1",
        "filedelete-legend": "Delete file",
        "filedelete-intro": "You are about to delete the file <strong>[[Media:$1|$1]]</strong> along with all of its history.",
        "pageinfo-article-id": "Page ID",
        "pageinfo-language": "Page content language",
        "pageinfo-content-model": "Page content model",
+       "pageinfo-content-model-change": "change",
        "pageinfo-robot-policy": "Indexing by robots",
        "pageinfo-robot-index": "Allowed",
        "pageinfo-robot-noindex": "Disallowed",
index a298d35..10f6c1b 100644 (file)
        "yourpasswordagain": "Retajpu pasvorton",
        "createacct-yourpasswordagain": "Konfirmu pasvorton",
        "createacct-yourpasswordagain-ph": "Retajpu pasvorton",
-       "remembermypassword": "Memori mian ensalutadon ĉe ĉi tiu komputilo (daŭrante maksimume $1 {{PLURAL:$1|tagon|tagojn}})",
        "userlogin-remembermypassword": "Memori mian ensaluton",
        "userlogin-signwithsecure": "Uzu sekurigitan konekton",
        "cannotloginnow-title": "Nuntempe ne eblas ensaluti",
index 38de0a1..dfab8c9 100644 (file)
        "yourpasswordagain": "Confirma la contraseña:",
        "createacct-yourpasswordagain": "Confirma la contraseña",
        "createacct-yourpasswordagain-ph": "Repite la contraseña",
-       "remembermypassword": "Mantenerme conectado en este navegador (hasta $1 {{PLURAL:$1|día|días}})",
        "userlogin-remembermypassword": "Mantener mi sesión iniciada",
        "userlogin-signwithsecure": "Usar conexión segura",
        "cannotloginnow-title": "No se puede iniciar sesión ahora",
        "action-import": "importar páginas desde otro wiki",
        "action-importupload": "importar páginas mediante la carga de un archivo",
        "action-patrol": "marcar ediciones de otros como verificadas",
-       "action-autopatrol": "tener tus ediciones marcadas como verificadas",
+       "action-autopatrol": "marcar como verificadas tus propias ediciones",
        "action-unwatchedpages": "ver la lista de páginas no vigiladas",
        "action-mergehistory": "fusionar el historial de esta página",
        "action-userrights": "modificar todos los permisos de usuario",
        "filerevert-submit": "Revertir",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong> ha sido revertido a la [$4 versión del $2 a las $3].",
        "filerevert-badversion": "No existe versión local previa de este archivo con esa marca de tiempo.",
+       "filerevert-identical": "La versión actual del archivo ya es idéntica a la seleccionada.",
        "filedelete": "Borrar $1",
        "filedelete-legend": "Borrar archivo",
        "filedelete-intro": "Estás por borrar el archivo <strong>[[Media:$1|$1]]</strong> así como todo su historial.",
        "logentry-delete-revision": "$1 {{GENDER:$2|modificó}} la visibilidad de {{PLURAL:$5|una revisión |$5 revisiones}} en la página  $3: $4",
        "logentry-delete-event-legacy": "$1 ha {{GENDER:$2|cambiado}} la visibilidad de eventos del registro en $3",
        "logentry-delete-revision-legacy": "$1 ha {{GENDER:$2|cambiado}} la visibilidad de las revisiones en la página $3",
-       "logentry-suppress-delete": "$1 {{GENDER:$2|borró}} la página $3",
+       "logentry-suppress-delete": "$1 {{GENDER:$2|suprimió}} la página $3",
        "logentry-suppress-event": "$1 {{GENDER:$2|modificó}} secretamente la visibilidad de {{PLURAL:$5|un evento|$5 eventos}} del registro en $3: $4",
        "logentry-suppress-revision": "$1 {{GENDER:$2|modificó}} secretamente la visibilidad de {{PLURAL:$5|una edición|$5 ediciones}} en la página $3: $4",
        "logentry-suppress-event-legacy": "$1 {{GENDER:$2|modificó}} secretamente la visibilidad de los eventos del registro en $3",
index 3b4e14f..d544a4e 100644 (file)
@@ -46,6 +46,7 @@
        "tog-watchdefault": "Lisa jälgimisloendisse minu muudetud leheküljed ja failid",
        "tog-watchmoves": "Lisa jälgimisloendisse minu teisaldatud leheküljed ja failid",
        "tog-watchdeletion": "Lisa jälgimisloendisse minu kustutatud leheküljed ja failid",
+       "tog-watchuploads": "Lisa jälgimisloendisse uued failid, mille olen üles laadinud",
        "tog-watchrollback": "Lisa jälgimisloendisse leheküljed, kus olen muudatuse tühistanud",
        "tog-minordefault": "Märgi kõik parandused vaikimisi pisiparandusteks",
        "tog-previewontop": "Näita eelvaadet toimetamiskasti ees",
        "yourpasswordagain": "Sisesta parool uuesti:",
        "createacct-yourpasswordagain": "Parooli kinnitus",
        "createacct-yourpasswordagain-ph": "Sisesta uuesti parool",
-       "remembermypassword": "Jäta parool meelde (kuni $1 {{PLURAL:$1|päevaks|päevaks}})",
        "userlogin-remembermypassword": "Jää sisseloginuks",
        "userlogin-signwithsecure": "Kasuta turvalist ühendust",
        "yourdomainname": "Sinu domeen:",
        "minoredit": "See on pisiparandus",
        "watchthis": "Jälgi seda lehekülge",
        "savearticle": "Salvesta",
+       "savechanges": "Salvesta",
        "publishpage": "Avalda lehekülg",
        "publishchanges": "Avalda muudatused",
        "preview": "Eelvaade",
        "undeletedrevisions": "$1 {{PLURAL:$1|redaktsioon|redaktsiooni}} taastatud",
        "undeletedrevisions-files": "{{PLURAL:$1|1 redaktsioon|$1 redaktsiooni}} ja {{PLURAL:$2|1 fail|$2 faili}} taastatud",
        "undeletedfiles": "{{PLURAL:$1|1 fail|$1 faili}} taastatud",
-       "cannotundelete": "Taastamine ebaõnnestus:\n$1",
+       "cannotundelete": "Taastamine ebaõnnestus osaliselt või täielikult:\n$1",
        "undeletedpage": "'''$1 on taastatud'''\n\n[[Special:Log/delete|Kustutamise logist]] võib leida loendi viimastest kustutamistest ja taastamistest.",
        "undelete-header": "Hiljuti kustutatud leheküljed leiad [[Special:Log/delete|kustutamislogist]].",
        "undelete-search-title": "Kustutatud lehekülgede otsimine",
        "sp-contributions-newbies-sub": "Uute kontode kaastöö",
        "sp-contributions-newbies-title": "Uute kasutajate kaastöö",
        "sp-contributions-blocklog": "blokeerimised",
-       "sp-contributions-suppresslog": "varjatud kaastöö",
-       "sp-contributions-deleted": "kustutatud kaastöö",
+       "sp-contributions-suppresslog": "{{GENDER:$1|varjatud}} kaastöö",
+       "sp-contributions-deleted": "{{GENDER:$1|kustutatud}} kaastöö",
        "sp-contributions-uploads": "üleslaadimised",
        "sp-contributions-logs": "logid",
        "sp-contributions-talk": "arutelu",
index 083585f..523b2ef 100644 (file)
        "yourpasswordagain": "Pasahitza berriz",
        "createacct-yourpasswordagain": "Pasahitza berridatzi",
        "createacct-yourpasswordagain-ph": "Sartu pasahitza berriro ere",
-       "remembermypassword": "Nire saioa ordenagailu honetan gogoratu ({{PLURAL:$1|egun baterako|$1 egunetarako }} gehienez)",
        "userlogin-remembermypassword": "Manten nazazu barruan",
        "userlogin-signwithsecure": "Erabili konexio ziurra",
        "yourdomainname": "Zure domeinua",
        "createacct-another-realname-tip": "Benetako izena hautazkoa da.\nEmatea erabakitzen baduzu hori erabiliko da lanaren atribuzioa egiterako garaian.",
        "pt-login": "Saioa hasi",
        "pt-login-button": "Saioa hasi",
+       "pt-login-continue-button": "Konexioa jarraitu",
        "pt-createaccount": "Sortu kontua",
        "pt-userlogout": "Saioa itxi",
        "php-mail-error-unknown": "PHPren mail() funtzioan arazo ezezagun bat egon da.",
        "resetpass_submit": "Pasahitza definitu eta saioa hasi",
        "changepassword-success": "Zure pasahitza aldatu da!",
        "changepassword-throttled": "Saioa hasteko saiakera gehiegi egin berri dituzu.\nBerriro saiatu aurretik $1 itxoin, mesedez.",
+       "botpasswords": "Bot pasahitzak",
+       "botpasswords-disabled": "Bot pasahitzak desgaituak daude.",
        "botpasswords-label-appid": "Bot izena:",
        "botpasswords-label-create": "Sortu",
        "botpasswords-label-update": "Eguneratu",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" lankidea ez dago erregistatuta. Mesedez, konprobatu orri hau editatu/sortu nahi duzun.",
        "userpage-userdoesnotexist-view": "\"$1\" erabiltzaile-kontua ez dago erregistraturik.",
        "blocked-notice-logextract": "Erabiltzaile hau blokeatuta dago une honetan.\nAzken blokeoaren erregistroa ageri da behean, erreferentzia gisa:",
-       "clearyourcache": "'''Oharra:''' Gorde ondoren, zure nabigatzailearen katxea ekidin beharko duzu aldaketak ikusteko.\n* '''Firefox / Safari:''' ''Shift'' tekla sakatu birkargatzeko momentuan, edo ''Ctrl-Shift-R'' edo ''Crtl-F5'' sakatu (''⌘-R''' Mac batean)\n* '''Google Chrome:''' ''Ctrl-Shift-R'' sakatu (''⌘-Shift-R'' Mac batean)\n* '''Internet Explorer:''' ''Ctrl'' tekla sakatu birkargatzeko momentuan, edo ''Ctrl-F5'' sakatu\n* '''Opera''' erabiltzaileek ''Tresnak → Hobespenak'' atalera joan eta katxea garbitzeko aukera hautatu",
+       "clearyourcache": "<strong>Oharra:</strong> Gorde ondoren, zure nabigatzailearen katxea ekidin beharko duzu aldaketak ikusteko.\n* <strong>Firefox / Safari:</strong> <em>Shift</em> tekla sakatu birkargatzeko momentuan, edo <em>Ctrl-Shift-R</em> edo <em>Crtl-F5</em>  sakatu (<em>⌘-R</em> Mac batean)\n* <strong>Google Chrome:</strong> <em>Ctrl-Shift-R </em>  sakatu (<em>⌘-Shift-R</em> Mac batean)\n* <strong>Internet Explorer:</strong> <em>Ctrl</em> tekla sakatu birkargatzeko momentuan, edo <em>Ctrl-F5</em> sakatu\n* <strong>Opera</strong> erabiltzaileek <em>Tresnak → Hobespenak</em> atalera joan eta katxea garbitzeko aukera hautatu",
        "usercssyoucanpreview": "'''Laguntza:''' Zure CSS berria gorde aurretik probatzeko \"{{int:showpreview}}\" botoia erabili.",
        "userjsyoucanpreview": "'''Laguntza:''' Zure JS berria gorde aurretik probatzeko \"{{int:showpreview}}\" botoia erabili.",
        "usercsspreview": "'''Ez ahaztu zure CSS kodea aurreikusten zabiltzala.'''\n'''Oraindik gorde gabe dago!'''",
        "previewnote": "'''Gogoratu hau aurrikuspen bat dela.'''\nZure aldaketak ez dira oraindik gorde!",
        "continue-editing": "Edizio-eremura joan",
        "previewconflict": "Aurreikuspenak aldaketen koadroan idatzitako testua erakusten du, gorde ondoren agertuko den bezala.",
-       "session_fail_preview": "'''Sentitzen dugu! Ezin izan da zure aldaketa prozesatu, saioko datu batzuen galera dela-eta. Mesedez, saiatu berriz. Arazoak jarraitzen badu, saiatu saioa amaitu eta berriz hasten.'''",
+       "session_fail_preview": "'''Sentitzen dugu! Ezin izan da zure aldaketa prozesatu, saioko datu batzuen galera dela-eta. Mesedez, saiatu berriz. Arazoak jarraitzen badu, saiatu [[Special:UserLogout|saioa amaitu]] eta berriz hasten.'''",
        "session_fail_preview_html": "'''Sentitzen dugu! Ezin izan dugu zure aldaketa burutu, saio datu galera bat medio.'''\n\n''Wiki honek HTML kodea onartzen duenez, aurreikuspena ezgaituta dago JavaScript erasoak saihestu asmoz.''\n\n'''Aldaketa saiakera hau zuzena baldin bada, saiatu berriro mesedez. Arazoak jarraitzen badu, saiatu saioa itxi eta berriz hasten.'''",
        "token_suffix_mismatch": "'''Zure aldaketa ezeztatua izan da zure bezeroak puntuazio-karaktereak itxuragabetu dituelako.\nAldaketa ezeztatua izan da testuaren galtzea galarazteko.\nHau batzuetan gertatzen da buggyan oinarritutako web proxy zerbitzua erabiltzean.'''",
        "edit_form_incomplete": "'''Aldaketa formularioaren atal batzuk ez dira iritsi zerbitzarira; bi aldiz ziurtatu zure aldaketak osorik daudela eta berriro saiatu.'''",
        "right-deletedtext": "Ikusi ezabatutako testua eta ezabatutako berrikuspenen arteko aldaketak",
        "right-browsearchive": "Ezabatutako orrialdeak bilatu",
        "right-undelete": "Ezabatutako orrialde bat itzularazi",
-       "right-suppressrevision": "Administratzaileentzat izkutatutako berrikuspenak berrikusi edo berrezarri",
+       "right-suppressrevision": "Edozein erabiltzaileren berrikuspenak ikusi, ezkutatu ala ikustarazi",
        "right-suppressionlog": "Log pribatuak ikusi",
        "right-block": "Blokeatu beste erabiltzaile batzuk, edita ez dezaten",
        "right-blockemail": "Erabiltzaile bati blokeatu mezu elektronikoak bidaltzeko aukera",
        "undeletedrevisions": "{{PLURAL:$1|Berrikuspen 1 leheneratu da|$1 berrikuspen leheneratu dira}}",
        "undeletedrevisions-files": "{{PLURAL:$1|berrikuspen|berrikuspen}} eta {{PLURAL:$2|fitxategi|fitxategi}} leheneratu dira",
        "undeletedfiles": "{{PLURAL:$1|fitxategi|fitxategi}} leheneratu dira",
-       "cannotundelete": "Ezabatutako birgaitzean akatsa: $1",
+       "cannotundelete": "Ezabatutako birgaitze betean edo hainbatetan akatsa: $1",
        "undeletedpage": "'''«$1» leheneratu da'''\n\nAzken ezabatze eta leheneratzeak ikusteko, jo ezazu [[Special:Log/delete|ezabaketa erregistrora]].",
        "undelete-header": "Berriki ezabatutako orriak ikusteko, jo ezazu [[Special:Log/delete|ezabaketa erregistrora]].",
        "undelete-search-title": "Ezabatutako orrialdeak bilatu",
        "sp-contributions-newbies-sub": "Hasiberrientzako",
        "sp-contributions-newbies-title": "Lankideen ekarpenak lankide berrietn",
        "sp-contributions-blocklog": "Blokeaketa erregistroa",
-       "sp-contributions-suppresslog": "lankide-ekarpen ezabatuak",
+       "sp-contributions-suppresslog": "{{GENDER:$1|(r)en}} lankide-ekarpen ezabatuak",
        "sp-contributions-deleted": "lankide-ekarpen ezabatuak",
        "sp-contributions-uploads": "igoerak",
        "sp-contributions-logs": "erregistroak",
index 56e8cbe..302bc58 100644 (file)
@@ -11,7 +11,8 @@
                        "Henares",
                        "MarcoAurelio",
                        "Macofe",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Crucifunked"
                ]
        },
        "tog-underline": "Surrayal atihus:",
        "oct": "Otu",
        "nov": "Nov",
        "dec": "Dic",
+       "january-date": "$1 eneru",
+       "february-date": "$1 de hebreru",
+       "march-date": "$1 de marçu",
+       "april-date": "$1 e abril",
+       "may-date": "$1 e mayu",
+       "june-date": "$1 e húniu",
+       "july-date": "$1 e húliu",
+       "august-date": "$1 e agostu",
+       "september-date": "$1 e setiembri",
+       "october-date": "$1 e outubri",
+       "november-date": "$1 e noviembri",
+       "december-date": "$1 e diziembri",
+       "period-am": "AM",
+       "period-pm": "PM",
        "pagecategories": "{{PLURAL:$1|Categoria|Categorias}}",
        "category_header": "Artículus ena categoria \"$1\"",
        "subcategories": "Sucategorias",
        "category-file-count": "{{PLURAL:$2|Esta categoria solu contiini el siguienti archivu.|{{PLURAL:$1|El siguienti archivu está|Los siguientis $1 archivus están}} nesta categoria, dun total de $2.}}",
        "category-file-count-limited": "{{PLURAL:$1|El siguienti archivu está|Los siguientis $1 archivus están}} nesta categoria.",
        "listingcontinuesabbrev": "acont.",
+       "broken-file-category": "Páhinas con atihus esgalçaos a archivus",
        "about": "Al tentu",
        "article": "Artículu",
        "newwindow": "(s'abrirá nuna nueva ventana)",
        "cancel": "Cancelal",
        "moredotdotdot": "Mas...",
-       "mypage": "La mi páhina",
+       "morenotlisted": "Esta lista nu está completa",
+       "mypage": "Páhina",
        "mytalk": "La mi caraba",
-       "anontalk": "Caraba pa esta IP",
+       "anontalk": "La mi caraba",
        "navigation": "Güiquipeandu",
        "and": "&#32;i",
        "qbfind": "Alcuentral",
        "actions": "Acionis",
        "namespaces": "Espáciu nombris",
        "variants": "Variantis",
+       "navigation-heading": "Menú de navegación",
        "errorpagetitle": "Marru",
        "returnto": "Gorvel a $1.",
        "tagline": "Dendi {{SITENAME}}",
        "printableversion": "Velsión pa imprental",
        "permalink": "Atiju remanenti",
        "print": "Imprental",
+       "view": "Guipal",
+       "view-foreign": "Vel en $1",
        "edit": "Eital",
+       "edit-local": "Eital descrición local",
        "create": "Crial",
+       "create-local": "Azeñil descrición local",
        "editthispage": "Eital esta páhina",
        "create-this-page": "Crial esta páhina",
        "delete": "Esborral",
        "deletethispage": "Esborral esta páhina",
+       "undeletethispage": "Arrecuperal esta páhina",
        "undelete_short": "Arrecuperal {{PLURAL:$1|una eición|$1 eicionis}}",
+       "viewdeleted_short": "Guipal {{PLURAL:$1|una eición esborrá|$1 eicionis esborrás}}",
        "protect": "Protegel",
        "protect_change": "escambial",
        "protectthispage": "Protegel esta página",
-       "unprotect": "esprotegel",
-       "unprotectthispage": "Esprotegel esta página",
+       "unprotect": "Escambial proteción",
+       "unprotectthispage": "Escambial la proteción esta página",
        "newpage": "Páhina nueva",
        "talkpage": "Palral sobri esta páhina",
        "talkpagelinktext": "Caraba",
        "otherlanguages": "En otras palras",
        "redirectedfrom": "(Rederihiu dendi $1)",
        "redirectpagesub": "Rederihil páhina",
+       "redirectto": "Redirihi a:",
        "lastmodifiedat": "Los úrtimus chambus desta páhina huerun a las $2 el dia $1.",
        "viewcount": "Esta páhina á siu visoreá {{PLURAL:$1|una vezi|$1 vezis}}.",
        "protectedpage": "Página protegia",
        "jumptosearch": "landeal",
        "aboutsite": "Al tentu {{SITENAME}}",
        "aboutpage": "Project:Enjolmación",
-       "copyright": "Continiu disponibri bahu $1.",
+       "copyright": "El continiu está disponibri bahu $1 a nu sel que se diga lo contrariu.",
        "copyrightpage": "{{ns:project}}:Copyright",
        "currentevents": "La trohi las notícias",
        "currentevents-url": "Project:La trohi las notícias",
        "disclaimers": "Avissu legal",
        "disclaimerpage": "Project:Arrayu heneral de responsabiliá",
        "edithelp": "Ayua d'eición",
+       "helppage-top-gethelp": "Ayua",
        "mainpage": "Página prencipal",
        "mainpage-description": "Páhina prencipal",
        "policy-url": "Project:Pulítica",
        "ok": "Dalcuerdu",
        "retrievedfrom": "Arrecuperau dendi \"$1\"",
        "youhavenewmessages": "Tiinis $1 ($2).",
+       "youhavenewmessagesmanyusers": "Tienis $1 de muchus usuárius ($2).",
+       "newmessageslinkplural": "{{PLURAL:$1|un mensahi nuevu|999=mensahis nuevus}}",
        "youhavenewmessagesmulti": "Tiinis nuevus mensahis en $1",
        "editsection": "eital",
        "editold": "eital",
        "yourname": "Nombri d'usuáriu:",
        "yourpassword": "Consínia:",
        "yourpasswordagain": "Escrebi e nuevu la consínia:",
-       "remembermypassword": "Recordal la mi cuenta nesti ordinaol (for a maximum of $1 {{PLURAL:$1|day|days}})",
        "yourdomainname": "El tu domiñu:",
        "externaldberror": "Marru d'autentificación esterna e la basi e datus, u bien nu t'alcuentras autorizau p'atualizal la tu cuenta esterna.",
        "login": "Entral",
        "createaccount-title": "Criaeru e cuentas de {{SITENAME}}",
        "createaccount-text": "Alguien á criau una cuenta pa $2 en {{SITENAME}} ($4). La consínia pa \"$2\" es \"$3\".\nEberias entral ena tu cuenta i chambal la tu consínia.\n\nSi s'á criau la cuenta ebiu a angún marru, inora esti mensahi.",
        "loginlanguagelabel": "Palra: $1",
+       "pt-login": "Acedel",
+       "pt-createaccount": "Crial cuenta",
        "changepassword": "Chambal consínia",
        "resetpass_announce": "As entrau ena tu cuenta con una consínia temporal. Pol favol, escrebi una nueva consínia aquí:",
        "resetpass_text": "<!-- Aquí s´escrebi el testu -->",
        "undo-failure": "Nu es posibri eshazel la eición ebiu a que otru usuáriu á realizau una eición entelmeya.",
        "undo-norev": "La eición nu pué sel eshecha ebiu a que nu dessisti, u hue esborrá",
        "undo-summary": "Eshazel revisión $1 de [[Special:Contributions/$2|$2]] ([[User talk:$2|Caraba]])",
-       "cantcreateaccounttitle": "Nu es posibri crial la cuenta",
        "cantcreateaccount-text": "La criación de cuentas pol parti e la IP ('''$1''') á siu pará pol el usuáriu [[User:$3|$3]].\n\nLa razón dá pol $3 es ''$2''",
        "viewpagelogs": "Vel los rustrihus d´esta páhina",
        "nohistory": "Nu ai dengún estorial d´eicionis pa esta páhina.",
        "action-browsearchive": "landeal páginas esborrás",
        "action-undelete": "arrecuperal esta página",
        "nchanges": "$1 {{PLURAL:$1|chambu|chambus}}",
+       "enhancedrc-history": "Estorial",
        "recentchanges": "Úrtimus chambus",
        "recentchanges-legend": "Ocionis enos úrtimus chambus",
        "recentchanges-summary": "Sigui los úrtimus chambus d´esti güiqui nesta páhina.",
        "rcnotefrom": "Embahu se muestran los chambus hechus dendi el '''$2''' (hata el '''$1''').",
        "rclistfrom": "Muestral los chambus hechus dendi el $3 $2",
        "rcshowhideminor": "$1 eicionis chiqueninas",
+       "rcshowhideminor-hide": "Açonchal",
        "rcshowhidebots": "$1 bots",
+       "rcshowhidebots-show": "Muestral",
        "rcshowhideliu": "$1 usuárius rustrius",
+       "rcshowhideliu-hide": "Açonchal",
        "rcshowhideanons": "$1 usuárius anónimus",
+       "rcshowhideanons-hide": "Açonchal",
        "rcshowhidepatr": "$1 eicionis patrullás",
        "rcshowhidemine": "$1 las mis eicionis",
+       "rcshowhidemine-hide": "Açonchal",
        "rclinks": "Muestral los $1 úrtimus chambus enus $2 úrtimus dias<br />$3",
        "diff": "def",
        "hist": "estor",
        "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|usuáriu está|usuárius están}} vehilandu]",
        "rc_categories": "Arrayal a categorias (separás pol \"|\")",
        "rc_categories_any": "Cualisquiá",
+       "rc-change-size-new": "$1{{PLURAL:$1|byte|bytes}} dempués el chambu",
        "newsectionsummary": "/* $1 */ seción nueva",
        "rc-enhanced-expand": "muestral detallis (es mestel JavaScript)",
        "rc-enhanced-hide": "Açonchal detallis",
        "tooltip-upload": "Prencipial a empuntal",
        "tooltip-rollback": "\"Reveltil\" esborra las eicionis hechas a esta página pol úrtimu usuáriu con un click",
        "tooltip-undo": "\"Esjadel\" revierti ésta eición i abri el mó eición en mó previsoreal.\nÉstu premiti añiil una radón al estorial.",
+       "tooltip-summary": "Escribi un brevi resumen",
        "anonymous": "{{PLURAL:$1|Ussuáriu anónimu|Ussuárius anónimus}} en {{SITENAME}}",
        "siteuser": "{{SITENAME}} usuáriu $1",
        "lastmodifiedatby": "Esta páhina se chambó pol úrtima vezi a las $2, el dia $1 pol $3.",
        "spambot_username": "MediaWiki limpia-spam",
        "spam_reverting": "Revirtiendu a la úrtima velsión que nu contenga atihus a $1",
        "spam_blanking": "Tolas revisionis tienin atihus a $1, branqueandu",
+       "simpleantispam-label": "Compreba anti-spam.\n<strong>nu</strong> rellene estu!",
        "markaspatrolleddiff": "Aseñalal cumu patrullau",
        "markaspatrolledtext": "Aseñalal esti artículu cumu patrullau",
        "markedaspatrolled": "Aseñalal cumu patrullau",
index e265a0e..01c2892 100644 (file)
        "yourpasswordagain": "تکرار گذرواژه:",
        "createacct-yourpasswordagain": "گذرواژه را دوباره وارد کنید",
        "createacct-yourpasswordagain-ph": "گذرواژه را وارد کنید برای بار دوم",
-       "remembermypassword": "گذرواژه را (تا حداکثر $1 {{PLURAL:$1|روز|روز}}) در این رایانه به خاطر بسپار",
        "userlogin-remembermypassword": "من را واردشده نگه‌دار",
        "userlogin-signwithsecure": "از ورود امن استفاده کنید",
        "cannotloginnow-title": "الان امکان وررود به سامانه نیست",
        "minoredit": "این ویرایش، جزئی است",
        "watchthis": "پی‌گیری این صفحه",
        "savearticle": "صفحه ذخیره شود",
-       "savechanges": "ذخیرهٔ تغییرات",
+       "savechanges": "ذخیره کردن تغییرات",
        "publishpage": "انتشار صفحه",
        "publishchanges": "انتشار تغییرات",
        "preview": "پیش‌نمایش",
        "allinnamespace": "همهٔ صفحات (فضای نام $1)",
        "allpagessubmit": "برو",
        "allpagesprefix": "نمایش صفحه‌های دارای پیشوند:",
-       "allpagesbadtitle": "عÙ\86Ù\88اÙ\86 ØµÙ\81Ø­Ù\87Ù\94 Ø¯Ø§Ø¯Ù\87â\80\8cشدÙ\87 Ù\86اÙ\85عتبر Ø§Ø³Øª Û\8cا Ø§Û\8cÙ\86Ú©Ù\87 Ø¯Ø§Ø±Ø§Û\8c Ù¾Û\8cØ´Ù\88Ù\86دÛ\8c Ø¨Û\8cÙ\86â\80\8cزباÙ\86Û\8c Û\8cا Ø¨Û\8cÙ\86â\80\8cÙ\88Û\8cÚ©Û\8câ\80\8cاÛ\8c Ø§Ø³Øª. Ù\85Ù\85Ú©Ù\86 Ø§Ø³Øª Ù\86Ù\88Û\8cسÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ø¨Ø¯Ø§Ø±Ø¯ Ú©Ù\87 Ù\86Ù\85Û\8câ\80\8cتÙ\88اÙ\86 Ø§Ø² Ø¢Ù\86Ù\87ا در عنوان صفحات استفاده کرد.",
+       "allpagesbadtitle": "عÙ\86Ù\88اÙ\86 ØµÙ\81Ø­Ù\87Ù\94 Ø¯Ø§Ø¯Ù\87â\80\8cشدÙ\87 Ù\86اÙ\85عتبر Ø§Ø³Øª Û\8cا Ø§Û\8cÙ\86Ú©Ù\87 Ø¯Ø§Ø±Ø§Û\8c Ù¾Û\8cØ´Ù\88Ù\86دÛ\8c Ø¨Û\8cÙ\86â\80\8cزباÙ\86Û\8c Û\8cا Ø¨Û\8cÙ\86â\80\8cÙ\88Û\8cÚ©Û\8câ\80\8cاÛ\8c Ø§Ø³Øª. Ù\85Ù\85Ú©Ù\86 Ø§Ø³Øª Ù\86Ù\88Û\8cسÙ\87â\80\8cÙ\87اÛ\8cÛ\8c Ø¯Ø§Ø´ØªÙ\87 Û\8cاشد Ú©Ù\87 Ø§Ø² Ø¢Ù\86Ù\87ا Ù\86Ù\85Û\8câ\80\8cتÙ\88اÙ\86 در عنوان صفحات استفاده کرد.",
        "allpages-bad-ns": "{{SITENAME}} دارای فضای نام «$1» نیست.",
        "allpages-hide-redirects": "پنهان‌کردن تغییرمسیرها",
        "cachedspecial-viewing-cached-ttl": "شما در حال مشاهدهٔ نسخه‌ای از این صفحه که در میانگیر قرار دارد هستید که ممکن است برای $1 قبل باشد.",
index 74de0d5..dc60afe 100644 (file)
@@ -51,7 +51,8 @@
                        "Mikahama",
                        "01miki10",
                        "Matma Rex",
-                       "BiscuitMan"
+                       "BiscuitMan",
+                       "Alluk."
                ]
        },
        "tog-underline": "Linkkien alleviivaus:",
        "yourpasswordagain": "Salasana uudelleen:",
        "createacct-yourpasswordagain": "Vahvista salasana",
        "createacct-yourpasswordagain-ph": "Kirjoita salasana uudelleen",
-       "remembermypassword": "Muista kirjautumiseni tässä selaimessa (enintään $1 {{PLURAL:$1|päivä|päivää}})",
        "userlogin-remembermypassword": "Pidä minut kirjautuneena",
        "userlogin-signwithsecure": "Käytä salattua yhteyttä",
        "cannotloginnow-title": "Nyt ei voi kirjautua sisään",
        "userlogin-createanother": "Luo toinen käyttäjätunnus",
        "createacct-emailrequired": "Sähköpostiosoite",
        "createacct-emailoptional": "Sähköpostiosoite (vapaaehtoinen)",
-       "createacct-email-ph": "Anna sähköpostiosoitteesi",
+       "createacct-email-ph": "Kirjoita sähköpostiosoitteesi",
        "createacct-another-email-ph": "Lisää sähköpostiosoite",
        "createaccountmail": "Käytä satunnaista väliaikaissalasanaa ja lähetä se alla olevaan sähköpostiosoitteeseen",
        "createaccountmail-help": "Voidaan käyttää luomaan tunnus toiselle käyttäjälle ilman salasanan tietämistä.",
        "passwordtoopopular": "Tavanomaisen kaltaisia salasanoja ei saa käyttää. Valitse parempi ja yksilöllisempi salasana.",
        "password-name-match": "Salasanasi täytyy olla eri kuin käyttäjätunnuksesi.",
        "password-login-forbidden": "Tämän käyttäjänimen ja salasanan käyttö on estetty.",
-       "mailmypassword": "Uudista salasana",
+       "mailmypassword": "Hanki uusi salasana",
        "passwordremindertitle": "Uusi väliaikainen salasana {{GRAMMAR:elative|{{SITENAME}}}}",
        "passwordremindertext": "Joku IP-osoitteesta $1 pyysi {{GRAMMAR:partitive|{{SITENAME}}}} ($4) lähettämään uuden salasanan. Väliaikainen salasana käyttäjälle $2 on nyt $3. Kirjaudu sisään ja vaihda salasana. Väliaikainen salasana vanhenee {{PLURAL:$5|yhden päivän|$5 päivän}} kuluttua.\n\nJos joku muu on tehnyt tämän pyynnön, tai jos olet muistanut salasanasi ja et halua vaihtaa sitä, voit jättää tämän viestin huomiotta ja jatkaa vanhan salasanan käyttöä.",
        "noemail": "Käyttäjälle $1 ei ole määritelty sähköpostiosoitetta.",
        "botpasswords-label-update": "Päivitä",
        "botpasswords-label-cancel": "Peru",
        "botpasswords-label-delete": "Poista",
-       "botpasswords-label-resetpassword": "Uudista salasana",
+       "botpasswords-label-resetpassword": "Hanki uusi salasana",
        "botpasswords-label-grants": "Valittavissa olevat toimintaoikeudet:",
        "botpasswords-label-restrictions": "Käyttörajoitukset:",
        "botpasswords-label-grants-column": "Myönnetään",
        "resetpass-expired": "Salasanasi on vanhentunut. Valitse uusi salasana, jotta pääset kirjautumaan sisään.",
        "resetpass-expired-soft": "Salasanasi on vanhentunut ja se pitää uudistaa. Valitse uusi salasana nyt tai paina \"{{int:authprovider-resetpass-skip-label}}\", niin voit uudistaa salasanan myöhemmin.",
        "resetpass-validity-soft": "Salasanasi ei ole kelvollinen: $1\n\nValitse nyt uusi salasana tai paina \"{{int:authprovider-resetpass-skip-label}}\", niin voit vaihtaa sen myöhemmin.",
-       "passwordreset": "Salasanan uudistus",
+       "passwordreset": "Salasanan palauttaminen",
        "passwordreset-text-one": "Täytä tämä lomake uudistaaksesi salasanasi.",
-       "passwordreset-text-many": "{{PLURAL:$1|Täytä yksi kentistä, jotta saat väliaikaisen salasanan sähköpostitse.}}",
+       "passwordreset-text-many": "{{PLURAL:$1|Täytä yksi seuraavista kentistä, jolloin saat väliaikaisen salasanan sähköpostitse.}}",
        "passwordreset-disabled": "Salasanojen uudistaminen ei ole mahdollista tässä wikissä.",
        "passwordreset-emaildisabled": "Sähköpostitoiminnot on poistettu käytöstä tässä wikissä.",
        "passwordreset-username": "Käyttäjätunnus:",
index 6c2190a..ad2b303 100644 (file)
                        "Trial",
                        "Matma Rex",
                        "Dcausse",
-                       "Lucas"
+                       "Lucas",
+                       "Mabroukb"
                ]
        },
        "tog-underline": "Soulignement des liens :",
-       "tog-hideminor": "Masquer les modifications mineures dans les changements récents",
+       "tog-hideminor": "Masquer les modifications mineures dans les modifications récentes",
        "tog-hidepatrolled": "Masquer les modifications relues dans les modifications récentes",
        "tog-newpageshidepatrolled": "Masquer les pages relues dans la liste des nouvelles pages",
        "tog-hidecategorization": "Masquer la catégorisation des pages",
        "yourpasswordagain": "Confirmez le mot de passe :",
        "createacct-yourpasswordagain": "Confirmez le mot de passe",
        "createacct-yourpasswordagain-ph": "Entrez à nouveau le mot de passe",
-       "remembermypassword": "Mémoriser mes données de connection avec ce navigateur (durant au maximum $1 jour{{PLURAL:$1||s}})",
        "userlogin-remembermypassword": "Garder ma session active",
        "userlogin-signwithsecure": "Utiliser une connexion sécurisée",
+       "cannotlogin-title": "Impossible de se connecter",
+       "cannotlogin-text": "La connexion n’est pas possible.",
        "cannotloginnow-title": "Impossible de se connecter maintenant",
        "cannotloginnow-text": "La connexion n’est pas possible en utilisant $1.",
+       "cannotcreateaccount-title": "Création de comptes impossible",
+       "cannotcreateaccount-text": "La création directe de comptes utilisateurs n’est pas activée sur ce wiki.",
        "yourdomainname": "Votre domaine :",
        "password-change-forbidden": "Vous ne pouvez pas modifier les mots de passe sur ce wiki.",
        "externaldberror": "Soit une erreur s’est produite sur la base de données d’authentification, soit vous n’êtes pas autorisé à mettre à jour votre compte externe.",
        "previewerrortext": "Une erreur s’est produite lors de la tentative de prévisualisation de vos modifications.",
        "blockedtitle": "L’utilisateur est bloqué.",
        "blockedtext": "'''Votre compte utilisateur ou votre adresse IP a été bloqué.'''\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : ''$2''.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité n’a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez préciser ces indications dans toutes les requêtes que vous ferez.",
-       "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n:''$2''\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité d’envoi de courriel que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité n’a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez préciser ces indications dans toutes les requêtes que vous ferez.",
+       "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n:<em>$2:</em>\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité d’envoi de courriel que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité n’a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez préciser ces indications dans toutes les requêtes que vous ferez.",
        "blockednoreason": "aucune raison donnée",
        "whitelistedittext": "Vous devez vous $1 pour avoir la permission de modifier le contenu.",
        "confirmedittext": "Vous devez confirmer votre adresse de courriel avant de modifier les pages.\nVeuillez entrer et valider votre adresse de courriel dans vos [[Special:Preferences|préférences]].",
        "missing-revision": "La révision nº $1 de la page intitulée « {{FULLPAGENAME}} » n’existe pas.\n\nCela survient en général en suivant un lien historique obsolète vers une page qui a été supprimée.\nVous pouvez trouver plus de détails dans le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} journal des suppressions].",
        "userpage-userdoesnotexist": "Le compte utilisateur « <nowiki>$1</nowiki> » n’est pas enregistré. Veuillez vérifier que vous voulez créer cette page.",
        "userpage-userdoesnotexist-view": "Le compte utilisateur « $1 » n'est pas enregistré.",
-       "blocked-notice-logextract": "Cet utilisateur est actuellement bloqué.\nLa dernière entrée du journal des blocages est indiquée ci-dessous à titre d’information :",
+       "blocked-notice-logextract": "Cet utilisateur est actuellement bloqué.\nLa dernière entrée du journal des blocages est affichée ci-dessous pour référence :",
        "clearyourcache": "<strong>Note :</strong> après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.\n* <strong>Firefox / Safari :</strong> maintenez la touche <em>Maj</em> (<em>Shift</em>) en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> sur un Mac) \n* <strong>Google Chrome :</strong> appuyez sur <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> sur un Mac) \n* <strong>Internet Explorer :</strong> maintenez la touche <em>Ctrl</em> en cliquant sur le bouton <em>Actualiser</em> ou pressez <em>Ctrl-F5</em> \n* <strong>Opera :</strong> allez dans <em>Menu → Settings</em> (<em>Opera → Préférences</em> sur un Mac) et ensuite à <em>Confidentialité & sécurité → Effacer les données d’exploration → Images et fichiers en cache</em>.",
        "usercssyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille CSS avant de l’enregistrer.",
        "userjsyoucanpreview": "<strong>Astuce :</strong> utilisez le bouton « {{int:showpreview}} » pour tester votre nouvelle feuille JavaScript avant de l’enregistrer.",
        "explainconflict": "Cette page a été changée après que vous ayez commencé à la modifier.\nLa zone de modification supérieure contient le texte tel qu’il est actuellement enregistré dans la base de données.\nVos modifications apparaissent dans la zone de modification inférieure.\nVous allez devoir fusionner vos modifications dans le texte existant.\n<strong>Seul</strong> le texte de la zone supérieure sera sauvegardé si vous cliquez sur « {{int:savearticle}} ».",
        "yourtext": "Votre texte",
        "storedversion": "La version enregistrée",
-       "nonunicodebrowser": "<strong>Attention : votre navigateur ne supporte pas l’Unicode.</strong>\nUn palliatif est en place vous permettant de modifier les pages en toute sécurité, faisant apparaître les caractères non-ASCII  sous forme hexadécimale dans la boîte de modification.",
+       "nonunicodebrowser": "<strong>Attention : votre navigateur ne prend pas en charge l’Unicode.</strong>\nUn palliatif est en place vous permettant de modifier les pages en toute sécurité, faisant apparaître les caractères non-ASCII sous forme hexadécimale dans la boîte de modification.",
        "editingold": "<strong>Attention : vous êtes en train de modifier une ancienne version de cette page.</strong>\nSi vous la publiez, toutes les modifications effectuées depuis cette version seront perdues.",
        "yourdiff": "Différences",
        "copyrightwarning": "Toutes les contributions à {{SITENAME}} sont considérées comme publiées sous les termes de la $2 (voir $1 pour plus de détails). \nSi vous ne désirez pas que vos écrits soient modifiés et distribués à volonté, merci de ne pas les soumettre ici.<br /> \nVous nous promettez aussi que vous avez écrit ceci vous-même, ou que vous l’avez copié d’une source provenant du domaine public ou d’une ressource libre similaire. \n<strong>N’UTILISEZ PAS DE TRAVAUX SOUS DROIT D’AUTEUR SANS AUTORISATION EXPRESSE !</strong>",
        "last": "diff",
        "page_first": "première",
        "page_last": "dernière",
-       "histlegend": "Diff de sélection: cochez les boîtes radio des révisions à comparer et appuyez sur entrée ou sur le bouton en bas.<br />\nLégende: <strong>({{int:cur}})</strong> = différence avec la dernière révision, <strong>({{int:last}})</strong> = différence avec la précédente révision, <strong>{{int:minoreditletter}}</strong> = modification mineure.",
+       "histlegend": "Diff de sélection : cochez les boîtes radio des versions à comparer et appuyez sur entrée ou sur le bouton en bas.<br />\nLégende: <strong>({{int:cur}})</strong> = différence avec la dernière version, <strong>({{int:last}})</strong> = différence avec la précédente version, <strong>{{int:minoreditletter}}</strong> = modification mineure.",
        "history-fieldset-title": "Naviguer dans l’historique",
        "history-show-deleted": "Supprimés seulement",
        "histfirst": "les plus anciennes",
        "history-feed-description": "Historique des versions pour cette page sur le wiki",
        "history-feed-item-nocomment": "$1 le $2",
        "history-feed-empty": "La page demandée n'existe pas.\nElle a peut-être été effacée ou renommée.\nEssayez de [[Special:Search|rechercher sur le wiki]] pour trouver de nouvelles pages en rapport avec le sujet.",
-       "history-edit-tags": "Modifier les balises des révisions sélectionnées",
+       "history-edit-tags": "Modifier les balises des versions sélectionnées",
        "rev-deleted-comment": "(résumé de modification retiré)",
        "rev-deleted-user": "(nom d'utilisateur retiré)",
        "rev-deleted-event": "(détails de l’historique retirés)",
        "large-file": "Les fichiers importés ne devraient pas dépasser $1 ; \nce fichier fait $2.",
        "largefileserver": "La taille de ce fichier est supérieure au maximum autorisé par le serveur.",
        "emptyfile": "Le fichier que vous voulez importer semble vide.\nCeci peut être dû à une erreur dans le nom du fichier.\nVeuillez vérifier que vous désirez vraiment importer ce fichier.",
-       "windows-nonascii-filename": "Ce wiki ne supporte pas les noms de fichiers avec des caractères spéciaux.",
+       "windows-nonascii-filename": "Ce wiki ne prend pas en charge les noms de fichiers avec des caractères spéciaux.",
        "fileexists": "Un fichier existe déjà sous ce nom.\nMerci de vérifier <strong>[[:$1]]</strong> si vous n'êtes pas certain{{GENDER:||e|}} de vouloir le remplacer.\n[[$1|thumb]]",
        "filepageexists": "La page de description pour ce fichier a déjà été créée ici <strong>[[:$1]]</strong>, mais aucun fichier n'existe actuellement sous ce nom.\nLe résumé que vous allez spécifier n'apparaîtra pas sur la page de description.\nPour que ce soit le cas, vous devrez modifier manuellement la page. \n[[$1|thumb]]",
        "fileexists-extension": "Un fichier existe avec un nom proche : [[$2|thumb]]\n* Nom du fichier à importer : <strong>[[:$1]]</strong>\n* Nom du fichier existant : <strong>[[:$2]]</strong>\nPeut-être voulez-vous utiliser un nom plus explicite ?",
        "file-thumbnail-no": "Le nom du fichier commence par <strong>$1</strong>.\nIl est possible qu'il s'agisse d'une version réduite <em>(vignette)</em>.\nSi vous disposez du fichier en haute résolution, importez-le, sinon veuillez modifier son nom.",
        "fileexists-forbidden": "Un fichier avec ce nom existe déjà et ne peut pas être écrasé.\nSi vous voulez toujours importer votre fichier, veuillez revenir en arrière et utiliser un autre nom. \n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Un fichier portant ce nom existe déjà dans le dépôt de fichiers partagé.\nSi vous voulez toujours importer votre fichier, veuillez revenir en arrière et utiliser un autre nom. \n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Le fichier téléchargé est une copie exacte de la version actuelle de <strong>[[:$1]]</strong>",
+       "fileexists-duplicate-version": "Le fichier téléversé est une copie exacte {{PLURAL:$2|d'une version précédente|de versions précédentes}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Ce fichier est un doublon {{PLURAL:$1|du fichier suivant|des fichiers suivants}} :",
        "file-deleted-duplicate": "Un fichier identique à celui-ci ([[:$1]]) a déjà été supprimé. \nVous devriez vérifier le journal des suppressions de ce fichier avant de l'importer à nouveau.",
        "file-deleted-duplicate-notitle": "Un fichier identique à ce fichier a déjà été supprimé ainsi que le titre. \nVous devriez demander à quelqu'un la possibilité de vérifier le journal de ce fichier supprimé afin d'examiner la situation  avant de l'importer à nouveau.",
        "upload-form-label-not-own-work-local-generic-local": "Vous pouvez aussi essayer [[Special:Upload|la page de téléchargement par défaut]].",
        "upload-form-label-own-work-message-generic-foreign": "Je comprends que je téléverse ce fichier vers un dépôt partagé. Je confirme agir en accord avec les conditions d’utilisation et les règles relatives aux licences de celui-ci.",
        "upload-form-label-not-own-work-message-generic-foreign": "Si vous n’êtes pas en mesure de téléverser ce fichier de façon conforme aux règles de ce dépôt partagé, veuillez fermer cette boîte de dialogue et essayer une autre méthode.",
-       "upload-form-label-not-own-work-local-generic-foreign": "Vous pouvez également essayer d’utiliser [[Special:Upload|la page de téléversement de {{SITENAME}}]], si leur règles de site autorisent le téléversement du fichier.",
+       "upload-form-label-not-own-work-local-generic-foreign": "Vous pouvez également essayer d’utiliser [[Special:Upload|la page de téléversement de {{SITENAME}}]], si leurs règles autorisent le téléversement du fichier.",
        "backend-fail-stream": "Impossible de lire le fichier \"$1\".",
        "backend-fail-backup": "Impossible de sauvegarder le fichier \"$1\".",
        "backend-fail-notexists": "Le fichier $1 n’existe pas.",
        "zip-file-open-error": "Une erreur s'est produite lors de l'ouverture du fichier ZIP pour contrôle.",
        "zip-wrong-format": "Le fichier spécifié n'est pas une archive ZIP.",
        "zip-bad": "Le fichier est une archive ZIP corrompue ou illisible.\nIl ne peut pas être correctement vérifié pour la sécurité.",
-       "zip-unsupported": "Le fichier est une archive ZIP qui utilise des caractéristiques non supportées par MediaWiki. \nSa sécurité ne peut pas être correctement vérifiée.",
+       "zip-unsupported": "Le fichier est une archive ZIP qui utilise des caractéristiques non prises en charge par MediaWiki.\nSa sécurité ne peut pas être correctement vérifiée.",
        "uploadstash": "Cache d’import",
        "uploadstash-summary": "Cette page donne accès aux fichiers qui sont importés (ou en cours d’importation), mais ne sont pas encore publiés dans le wiki. Ces fichiers ne sont pas encore visibles, sauf pour l’utilisateur qui les a importés.",
        "uploadstash-clear": "Effacer les fichiers en cache d'import",
        "uploadstash-exception": "Impossible de stocker le téléchargement dans la réserve ($1) : « $2 ».",
        "invalid-chunk-offset": "Offset de segment non valide",
        "img-auth-accessdenied": "Accès refusé",
-       "img-auth-nopathinfo": "PATH_INFO manquant.\nVotre serveur n'est pas paramétré pour transmettre cette information.\nIl fonctionne peut-être en CGI et ne supporte pas img_auth.\nVoir : https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+       "img-auth-nopathinfo": "PATH_INFO manquant.\nVotre serveur n’est pas paramétré pour transmettre cette information.\nIl fonctionne peut-être en CGI et ne prend pas en charge img_auth.\nVoir : https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "Le chemin demandé n'est pas le répertoire d'import configuré.",
        "img-auth-badtitle": "Impossible de construire un titre valide à partir de « $1 ».",
        "img-auth-nologinnWL": "Vous n'êtes pas connecté et « $1 » n'est pas dans la liste blanche.",
        "filerevert-submit": "Rétablir",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong> a été rétabli à [$4 la version du $2 à $3].",
        "filerevert-badversion": "Il n'y a pas localement de version antérieure du fichier qui porte la date indiquée.",
+       "filerevert-identical": "La version actuelle du fichier est déjà identique à celle sélectionnée.",
        "filedelete": "Supprimer $1",
        "filedelete-legend": "Supprimer le fichier",
        "filedelete-intro": "Vous êtes sur le point de supprimer <strong>[[Media:$1|$1]]</strong> ainsi que tout son historique.",
        "undeletedrevisions": "$1 {{PLURAL:$1|version restaurée|versions restaurées}}",
        "undeletedrevisions-files": "$1 version{{PLURAL:$1||s}} et $2 fichier{{PLURAL:$2||s}} restauré{{PLURAL:$2||s}}",
        "undeletedfiles": "$1 {{PLURAL:$1|fichier restauré|fichiers restaurés}}",
-       "cannotundelete": "Certaines ou toutes les restitutions ont échoué:\n$1",
+       "cannotundelete": "Certaines ou toutes les restaurations ont échoué :\n$1",
        "undeletedpage": "<strong>La page $1 a été restaurée.</strong>\n\nConsultez le [[Special:Log/delete|journal des suppressions]] pour obtenir la liste des récentes suppressions et restaurations.",
        "undelete-header": "Consultez le [[Special:Log/delete|journal des suppressions]] pour lister les pages récemment supprimées.",
        "undelete-search-title": "Rechercher les pages supprimées",
        "sp-contributions-logs": "journaux",
        "sp-contributions-talk": "discuter",
        "sp-contributions-userrights": "gérer les droits",
-       "sp-contributions-blocked-notice": "Cet utilisateur est actuellement bloqué. \nLa dernière entrée du journal des blocages est indiquée ci-dessous à titre d'information :",
-       "sp-contributions-blocked-notice-anon": "Cette adresse IP est actuellement bloquée.\nLa dernière entrée du journal des blocages est indiquée ci-dessous à titre d'information :",
+       "sp-contributions-blocked-notice": "Cet utilisateur est actuellement bloqué. \nLa dernière entrée du journal des blocages est affichée ci-dessous pour référence :",
+       "sp-contributions-blocked-notice-anon": "Cette adresse IP est actuellement bloquée.\nLa dernière entrée du journal des blocages est affichée ci-dessous pour référence :",
        "sp-contributions-search": "Rechercher les contributions",
        "sp-contributions-username": "Adresse IP ou nom d'utilisateur :",
        "sp-contributions-toponly": "Ne montrer que les contributions qui sont les dernières des articles",
        "blockipsuccesssub": "Blocage réussi",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]] a été bloqué{{GENDER:$1||e}}.<br />\nConsultez la [[Special:BlockList|liste des blocages]] pour revoir les blocages.",
        "ipb-blockingself": "Vous êtes sur le point de bloquer votre propre compte ! Êtes-vous certain{{GENDER:||e}} de vouloir faire cela ?",
-       "ipb-confirmhideuser": "Vous êtes sur le point de bloquer un utilisateur avec « cacher l'utilisateur » activé. Cela supprime le nom de l'utilisateur dans toutes les listes et les entrées du journal. Êtes-vous sûr{{GENDER:||e}} de vouloir le faire ?",
+       "ipb-confirmhideuser": "Vous êtes sur le point de bloquer un utilisateur avec « cacher l'utilisateur » activé. Cela supprimera le nom de l'utilisateur dans toutes les listes et les entrées du journal. Êtes-vous sûr{{GENDER:||e}} de vouloir le faire ?",
        "ipb-confirmaction": "Si vous êtes sûr{{GENDER:||e}} de vraiment vouloir le faire, veuillez cocher le champ « {{int:ipb-confirm}} » en bas.",
        "ipb-edit-dropdown": "Modifier les motifs de blocage par défaut",
        "ipb-unblock-addr": "Débloquer $1",
        "ipb-blocklist": "Voir les blocages existants",
        "ipb-blocklist-contribs": "Contributions pour {{GENDER:$1|$1}}",
        "ipb-blocklist-duration-left": "$1 restant",
-       "unblockip": "Débloquer un utilisateur ou une adresse IP",
+       "unblockip": "Débloquer un utilisateur",
        "unblockiptext": "Utilisez le formulaire ci-dessous pour redonner les droits d’écriture à une adresse IP ou un nom d’utilisateur qui a été bloqué auparavant.",
        "ipusubmit": "Supprimer ce blocage",
        "unblocked": "[[User:$1|$1]] a été débloqué{{GENDER:$1||e}}",
        "emaillink": "envoyer un courriel",
        "autoblocker": "Vous avez été bloqué automatiquement parce que votre adresse IP a été récemment utilisée par « [[User:$1|$1]] ».\nLe motif fourni pour le blocage de $1 est « $2 »",
        "blocklogpage": "Journal des blocages",
-       "blocklog-showlog": "Cet utilisateur a été bloqué précédemment. \nLe journal des blocages est disponible ci-dessous :",
-       "blocklog-showsuppresslog": "Cet utilisateur a été bloqué et masqué précédemment. \nLe journal des masquages est disponible ci-dessous :",
+       "blocklog-showlog": "Cet utilisateur a été bloqué précédemment. \nLe journal des blocages est affiché ci-dessous pour référence :",
+       "blocklog-showsuppresslog": "Cet utilisateur a été bloqué et masqué précédemment. \nLe journal des masquages est affiché ci-dessous pour référence :",
        "blocklogentry": "a bloqué [[$1]] ; expiration : $2 $3",
        "reblock-logentry": "a modifié les paramètres du blocage de [[$1]] avec une expiration au $2 $3",
        "blocklogtext": "Ceci est le journal des actions de blocage et déblocage d’utilisateurs.\nLes adresses IP automatiquement bloquées ne sont pas listées.\nConsultez la [[Special:BlockList|liste des blocages]] pour voir les bannissements et blocages effectivement en cours.",
        "movepagetalktext": "Si vous cochez cette case, la page de discussion associée sera automatiquement renommée, à moins qu’une page de discussion non vide existe déjà sous ce nouveau nom.\n\nDans ce cas, vous devrez renommer ou fusionner cette page de discussion manuellement si vous le désirez.",
        "moveuserpage-warning": "<strong>Attention :</strong> Vous êtes sur le point de renommer une page d’utilisateur. Veuillez noter que seule la page sera renommée et que l’utilisateur <em>ne</em> sera <em>pas</em> renommé.",
        "movecategorypage-warning": "<strong>Avertissement :</strong> Vous êtes sur le point de renommer une page de catégorie. Veuillez noter que seule la catégorie sera renommée et <em>qu’aucune</em> des pages de l’ancienne catégorie ne sera transférée dans la nouvelle.",
-       "movenologintext": "Pour pouvoir renommer une page, vous devez être [[Special:UserLogin|identifié{{GENDER:||e}}]] avec un compte utilisateur enregistré et d'ancienneté suffisante.",
+       "movenologintext": "Pour pouvoir renommer une page, vous devez être [[Special:UserLogin|identifié{{GENDER:||e}}]] avec un compte utilisateur enregistré.",
        "movenotallowed": "Vous n'avez pas la permission de renommer les pages.",
        "movenotallowedfile": "Vous n'avez pas la permission de renommer les fichiers.",
        "cant-move-user-page": "Vous n’avez pas la permission de renommer les pages principales des utilisateurs (sauf les sous-pages).",
        "cant-move-to-user-page": "Vous n’avez pas la permission de renommer une page vers une page utilisateur (mais vous pouvez le faire vers une sous-page utilisateur).",
-       "cant-move-category-page": "Vous n'avez pas la permis de renommer les pages de catégorie.",
+       "cant-move-category-page": "Vous n'avez pas la permission de renommer les pages de catégorie.",
        "cant-move-to-category-page": "Vous n'avez pas la permission de renommer une page vers une page de catégorie.",
        "newtitle": "Nouveau titre :",
        "move-watch": "Suivre les pages originale et nouvelle",
        "movepagebtn": "Renommer la page",
        "pagemovedsub": "Renommage réussi",
-       "movepage-moved": "'''« $1 » a été renommée en « $2 »'''",
+       "movepage-moved": "<strong>« $1 » a été renommée en « $2 »</strong>",
        "movepage-moved-redirect": "Une redirection depuis l'ancien nom a été créée.",
        "movepage-moved-noredirect": "La création d'une redirection depuis l'ancien nom a été annulée.",
        "articleexists": "Il existe déjà une page portant ce titre, ou le titre que vous avez choisi n'est pas correct.\nVeuillez en choisir un autre.",
        "cantmove-titleprotected": "Vous ne pouvez pas déplacer une page vers cet emplacement car la création de page avec ce nouveau titre a été protégée.",
        "movetalk": "Renommer aussi la page de discussion associée",
-       "move-subpages": "Renommer les sous-pages (jusqu'à $1 {{PLURAL:$1|page|pages}})",
-       "move-talk-subpages": "Renommer les sous-pages de la page de discussion (jusqu'à $1 pages)",
+       "move-subpages": "Renommer les sous-pages (maximum $1)",
+       "move-talk-subpages": "Renommer les sous-pages de la page de discussion (maximum $1)",
        "movepage-page-exists": "La page $1 existe déjà et ne peut pas être écrasée automatiquement.",
        "movepage-page-moved": "La page $1 a été renommée en $2.",
        "movepage-page-unmoved": "La page $1 n'a pas pu être renommée en $2.",
        "selfmove": "Les titres d'origine et de destination sont les mêmes ;\nimpossible de renommer une page sur elle-même.",
        "immobile-source-namespace": "Vous ne pouvez pas renommer les pages dans l'espace de noms « $1 »",
        "immobile-target-namespace": "Vous ne pouvez pas renommer des pages vers l’espace de noms « $1 ».",
-       "immobile-target-namespace-iw": "Les destinations interwikis ne sont pas une cible valide pour les déplacements.",
+       "immobile-target-namespace-iw": "Un lien interwiki n’est pas une cible valide pour un renommage de page.",
        "immobile-source-page": "Cette page n'est pas renommable.",
        "immobile-target-page": "Il n'est pas possible de renommer la page vers ce titre.",
        "bad-target-model": "La destination souhaitée utilise un autre modèle de contenu. Impossible de convertir de $1 vers $2.",
        "imageinvalidfilename": "Le nom du fichier cible est incorrect",
        "fix-double-redirects": "Mettre à jour les redirections pointant vers le titre original",
        "move-leave-redirect": "Laisser une redirection vers le nouveau titre",
-       "protectedpagemovewarning": "'''Attention :''' Cette page a été protégée afin que seuls les utilisateurs possédant les droits d'administrateur puissent la renommer. La dernière entrée du journal est affichée ci-dessous pour référence :",
-       "semiprotectedpagemovewarning": "'''Note :''' Cette page a été protégée afin que seuls les utilisateurs enregistrés puissent la renommer. La dernière entrée du journal est affichée ci-dessous pour référence :",
+       "protectedpagemovewarning": "<strong>Attention :</strong> Cette page a été protégée afin que seuls les utilisateurs possédant les droits d'administrateur puissent la renommer. \nLa dernière entrée du journal est affichée ci-dessous pour référence :",
+       "semiprotectedpagemovewarning": "<strong>Note :</strong> Cette page a été protégée afin que seuls les utilisateurs enregistrés puissent la renommer. \nLa dernière entrée du journal est affichée ci-dessous pour référence :",
        "move-over-sharedrepo": "[[:$1]] existe déjà sur un dépôt partagé. Renommer ce fichier rendra le fichier sur le dépôt partagé inaccessible.",
        "file-exists-sharedrepo": "Le nom choisi est déjà utilisé par un fichier sur un dépôt partagé.\nChoisissez un autre nom.",
        "export": "Exporter des pages",
-       "exporttext": "Vous pouvez exporter en XML le texte et l’historique d’une page ou d’un ensemble de pages ; le résultat peut alors être importé dans un autre wiki utilisant le logiciel MediaWiki via la [[Special:Import|page d’importation]].\n\nPour exporter des pages, entrez leurs titres dans la boîte de texte ci-dessous, à raison d’un titre par ligne. Sélectionnez si vous désirez la version actuelle avec toutes les anciennes versions, avec les lignes de l’historique de la page, ou simplement la page actuelle avec des informations sur la dernière modification.\n\nDans ce dernier cas vous pouvez aussi utiliser un lien, tel que [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] pour la page [[{{MediaWiki:Mainpage}}]].",
+       "exporttext": "Vous pouvez exporter en XML le texte et l’historique d’une page ou d’un ensemble de pages.\nLe résultat peut alors être importé dans un autre wiki utilisant le logiciel MediaWiki via la [[Special:Import|page d’importation]].\n\nPour exporter des pages, entrez leurs titres dans la boîte de texte ci-dessous, à raison d’un titre par ligne. Sélectionnez si vous désirez la version actuelle avec toutes les anciennes versions, avec les lignes de l’historique de la page, ou simplement la page actuelle avec des informations sur la dernière modification.\n\nDans ce dernier cas vous pouvez aussi utiliser un lien, tel que [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] pour la page [[{{MediaWiki:Mainpage}}]].",
        "exportall": "Exporter toutes les pages",
        "exportcuronly": "Exporter uniquement la version courante, sans l’historique complet",
        "exportnohistory": "----\n'''Note :''' l’exportation de l’historique complet des pages à l’aide de ce formulaire a été désactivée pour des raisons de performance.",
        "export-submit": "Exporter",
        "export-addcattext": "Ajouter les pages de la catégorie :",
        "export-addcat": "Ajouter",
-       "export-addnstext": "Ajouter des pages dans l'espace de noms :",
+       "export-addnstext": "Ajouter des pages de l'espace de noms :",
        "export-addns": "Ajouter",
        "export-download": "Enregistrer dans un fichier",
        "export-templates": "Inclure les modèles",
-       "export-pagelinks": "Inclure les pages liées à une profondeur de :",
+       "export-pagelinks": "Inclure les pages liées jusqu'à une profondeur de :",
        "export-manual": "Ajouter des pages manuellement :",
        "allmessages": "Messages système",
-       "allmessagesname": "Nom du message",
+       "allmessagesname": "Nom",
        "allmessagesdefault": "Message par défaut",
        "allmessagescurrent": "Message actuel",
        "allmessagestext": "Ceci est la liste des messages système disponibles dans l’espace de noms MediaWiki.\nVeuillez visiter la [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation Régionalisation de MediaWiki] et [https://translatewiki.net/ translatewiki.net] si vous désirez contribuer à la régionalisation générique de MediaWiki.",
-       "allmessagesnotsupportedDB": "Cette page '''{{ns:special}}:Allmessages''' n'est pas utilisable car '''$wgUseDatabaseMessages''' a été désactivé.",
+       "allmessagesnotsupportedDB": "Cette page n’est pas utilisable car <strong>$wgUseDatabaseMessages</strong> a été désactivé.",
        "allmessages-filter-legend": "Filtrer",
        "allmessages-filter": "Filtrer par état de modification :",
        "allmessages-filter-unmodified": "Non modifié",
        "thumbnail_invalid_params": "Paramètres de la miniature incorrects",
        "thumbnail_toobigimagearea": "Fichier avec des dimensions supérieures à $1",
        "thumbnail_dest_directory": "Impossible de créer le répertoire de destination",
-       "thumbnail_image-type": "Type d'image non supporté",
+       "thumbnail_image-type": "Type d’image non pris en charge",
        "thumbnail_gd-library": "Configuration incomplète de la bibliothèque GD : fonction $1 introuvable",
        "thumbnail_image-missing": "Le fichier suivant est introuvable : $1",
        "thumbnail_image-failure-limit": "Il y a eu récemment trop de tentatives échouées ($1 ou plus) pour restituer cette vignette. Veuillez réessayer ultérieurement.",
        "import-mapping-subpage": "Importer comme sous-pages de la page suivante :",
        "import-upload-filename": "Nom du fichier :",
        "import-comment": "Commentaire :",
-       "importtext": "Veuillez exporter le fichier depuis le wiki d'origine en utilisant son [[Special:Export|outil d'exportation]].\nSauvegardez-le sur votre disque dur puis importez-le ici.",
+       "importtext": "Veuillez exporter le fichier depuis le wiki d’origine en utilisant l’[[Special:Export|outil d'exportation]].\nSauvegardez-le sur votre disque dur puis importez-le ici.",
        "importstart": "Importation des pages…",
        "import-revision-count": "$1 version{{PLURAL:$1||s}}",
        "importnopages": "Aucune page à importer.",
        "importuploaderrortemp": "L'import du fichier a échoué.\nUn dossier temporaire est manquant.",
        "import-parse-failure": "Échec lors de l'analyse du XML à importer",
        "import-noarticle": "Aucune page à importer !",
-       "import-nonewrevisions": "Aucune révision importée (toutes étaient déjà présentes, ou ignorées du fait d’erreurs).",
+       "import-nonewrevisions": "Aucune révision importée (toutes étaient soit déjà présentes, soit ignorées du fait d’erreurs).",
        "xml-error-string": "$1 à la ligne $2, colonne $3 (octet $4) : $5",
        "import-upload": "Import de données XML",
        "import-token-mismatch": "Perte des données de session.\n\nVous avez peut-être été déconnecté. <strong>Veuillez vérifier que vous êtes toujours connecté et réessayez</strong>.\nSi cela ne fonctionne toujours pas, essayez de [[Special:UserLogout|vous déconnecter]] et reconnectez-vous, et vérifiez que votre navigateur accepte les cookies de ce site.",
        "import-error-special": "La page « $1 » n’a pas été importée parce qu’elle appartient à un espace de noms spécial qui n’autorise aucune page.",
        "import-error-invalid": "Page « $1 » n’a pas été importée parce que le nom sous lequel elle aurait été importée n’est pas valide sur ce wiki.",
        "import-error-unserialize": "La révision $2 de la page « $1 » ne peut pas être désérialisée. La révision est indiquée comme utilisant le modèle de contenu $3 sérialisé en $4.",
-       "import-error-bad-location": "La révision $2 utilisant le modèle de contenu $3 n’a pas pu être stocké sur « $1 » sur ce wiki, car ce modèle n’est pas supporté sur cette page.",
+       "import-error-bad-location": "La révision $2 utilisant le modèle de contenu $3 n’a pas pu être stockée sur « $1 » sur ce wiki, car ce modèle n’est pas pris en charge sur cette page.",
        "import-options-wrong": "{{PLURAL:$2|Mauvaise option|Mauvaises options}} : <nowiki>$1</nowiki>",
        "import-rootpage-invalid": "La page racine fournie est un titre non valide.",
        "import-rootpage-nosubpage": "L'espace de noms « $1 » de la page racine n'autorise pas les sous-pages.",
        "javascripttest-pagetext-unknownaction": "Action « $1 » inconnue.",
        "javascripttest-qunit-intro": "Voir [$1 la documentation de test] sur mediawiki.org.",
        "tooltip-pt-userpage": "{{GENDER:|Votre}} page utilisateur",
-       "tooltip-pt-anonuserpage": "La page utilisateur de l'IP avec laquelle vous contribuez",
+       "tooltip-pt-anonuserpage": "La page utilisateur avec l'adresse IP de laquelle vous contribuez",
        "tooltip-pt-mytalk": "{{GENDER:|Votre}} page de discussion",
        "tooltip-pt-anontalk": "La page de discussion pour les contributions depuis cette adresse IP",
        "tooltip-pt-preferences": "{{GENDER:|Vos}} préférences",
-       "tooltip-pt-watchlist": "La liste des pages dont vous suivez les modifications",
+       "tooltip-pt-watchlist": "Une liste des pages dont vous suivez les modifications",
        "tooltip-pt-mycontris": "La liste de {{GENDER:|vos}} contributions",
        "tooltip-pt-anoncontribs": "Une liste des modifications effectuées depuis cette adresse IP",
        "tooltip-pt-login": "Il est recommandé de vous identifier ; ce n'est cependant pas obligatoire.",
        "tooltip-n-recentchanges": "Liste des modifications récentes sur le wiki",
        "tooltip-n-randompage": "Afficher une page au hasard",
        "tooltip-n-help": "Aide",
-       "tooltip-t-whatlinkshere": "Liste des pages liées à celle-ci",
-       "tooltip-t-recentchangeslinked": "Liste des modifications récentes des pages liées à celle-ci",
+       "tooltip-t-whatlinkshere": "Liste des pages liées qui pointent sur celle-ci",
+       "tooltip-t-recentchangeslinked": "Liste des modifications récentes des pages appelées par celle-ci",
        "tooltip-feed-rss": "Flux RSS pour cette page",
        "tooltip-feed-atom": "Flux Atom pour cette page",
        "tooltip-t-contributions": "Voir la liste des contributions de {{GENDER:$1|cet utilisateur|cette utilisatrice}}",
        "tooltip-ca-nstab-template": "Voir le modèle",
        "tooltip-ca-nstab-help": "Voir la page d'aide",
        "tooltip-ca-nstab-category": "Voir la page de la catégorie",
-       "tooltip-minoredit": "Marquer mes modifications comme mineures",
+       "tooltip-minoredit": "Marquer ceci comme modification mineure",
        "tooltip-save": "Enregistrer vos modifications",
        "tooltip-publish": "Publier vos modifications",
        "tooltip-preview": "Merci de prévisualiser vos modifications avant de les publier",
-       "tooltip-diff": "Affiche les modifications que vous avez apportées au texte",
-       "tooltip-compareselectedversions": "Afficher les différences entre deux versions de cette page",
+       "tooltip-diff": "Afficher les modifications que vous avez apportées au texte",
+       "tooltip-compareselectedversions": "Afficher les différences entre les deux versions selectionnées de cette page",
        "tooltip-watch": "Ajouter cette page à votre liste de suivi",
        "tooltip-watchlistedit-normal-submit": "Enlever les titres",
        "tooltip-watchlistedit-raw-submit": "Mise à jour de la liste de suivi",
        "tooltip-recreate": "Recréer la page même si celle-ci a été effacée",
        "tooltip-upload": "Démarrer l'import",
-       "tooltip-rollback": "« Révoquer » annule en un clic la ou les modification(s) de cette page par son dernier contributeur.",
+       "tooltip-rollback": "« Révoquer » annule en un clic la ou les modification(s) de cette page réalisées par son dernier contributeur",
        "tooltip-undo": "« Annuler » rétablit la modification précédente et ouvre la fenêtre de modification en mode prévisualisation. Il est possible d’ajouter une raison dans le résumé.",
        "tooltip-preferences-save": "Sauvegarder les préférences",
        "tooltip-summary": "Entrez un bref résumé",
        "pageinfo-article-id": "Numéro de la page",
        "pageinfo-language": "Langue du contenu de la page",
        "pageinfo-content-model": "Modèle de contenu de la page",
+       "pageinfo-content-model-change": "modifier",
        "pageinfo-robot-policy": "Indexation par robots",
        "pageinfo-robot-index": "Autorisée",
        "pageinfo-robot-noindex": "Interdite",
        "pageinfo-magic-words": "{{PLURAL:$1|Mot magique|Mots magiques}} ($1)",
        "pageinfo-hidden-categories": "{{PLURAL:$1|Catégorie cachée|Catégories cachées}} ($1)",
        "pageinfo-templates": "{{PLURAL:$1|Modèle inclu|Modèles inclus}} ($1)",
-       "pageinfo-transclusions": "{{PLURAL:$1|Page dans laquelle|Pages dans lesquelles}} elle est incluse ($1)",
+       "pageinfo-transclusions": "{{PLURAL:$1|Page dans laquelle|Pages dans lesquelles}} cette page est incluse ($1)",
        "pageinfo-toolboxlink": "Information sur la page",
        "pageinfo-redirectsto": "Rediriger vers",
        "pageinfo-redirectsto-info": "info",
-       "pageinfo-contentpage": "Compté comme page de contenu",
+       "pageinfo-contentpage": "Comptée comme page de contenu",
        "pageinfo-contentpage-yes": "Oui",
-       "pageinfo-protect-cascading": "Les protections sont déduites d'ici",
+       "pageinfo-protect-cascading": "Les protections sont déduites à partir d'ici",
        "pageinfo-protect-cascading-yes": "Oui",
        "pageinfo-protect-cascading-from": "Les protections sont déduites depuis",
        "pageinfo-category-info": "Informations sur la catégorie",
        "filedelete-archive-read-only": "Le dossier d'archivage « $1 » n'est pas modifiable par le serveur.",
        "previousdiff": "← Modification précédente",
        "nextdiff": "Modification suivante →",
-       "mediawarning": "'''Attention :''' ce type de fichier peut contenir du code malveillant.\nSi vous l'exécutez, votre système peut être compromis.",
-       "imagemaxsize": "Taille maximale des images :<br />''(pour les pages de description de fichier)''",
+       "mediawarning": "<strong>Attention :</strong> ce type de fichier peut contenir du code malveillant.\nSi vous l'exécutez, votre système peut être compromis.",
+       "imagemaxsize": "Taille maximale des images :<br /><em>(pour les pages de description de fichier)</em>",
        "thumbsize": "Taille de la miniature :",
        "widthheight": "$1&nbsp;×&nbsp;$2",
        "widthheightpage": "$1 × $2, $3 page{{PLURAL:$3||s}}",
        "file-info-size-pages": "$1 × $2 pixels, taille de fichier : $3, type MIME : $4, $5 page{{PLURAL:$5||s}}",
        "file-nohires": "Pas de plus haute résolution disponible.",
        "svg-long-desc": "Fichier SVG, résolution de $1 × $2 pixels, taille : $3",
-       "svg-long-desc-animated": "Fichier SVG animé, taille $1 × $2 pixels, taille du fichier : $3",
+       "svg-long-desc-animated": "Fichier SVG animé, résolution $1 × $2 pixels, taille du fichier : $3",
        "svg-long-error": "Fichier SVG non valide : $1",
        "show-big-image": "Fichier d'origine",
        "show-big-image-preview": "Taille de cet aperçu : $1.",
        "file-info-png-looped": "en boucle",
        "file-info-png-repeat": "joué $1 {{PLURAL:$1|fois}}",
        "file-info-png-frames": "$1 {{PLURAL:$1|image|images}}",
-       "file-no-thumb-animation": "'''Remarque : En raison de limitations techniques, les vignettes de ce fichier ne seront pas animées.'''",
-       "file-no-thumb-animation-gif": "'''Remarque : En raison de limitations techniques, les vignettes d'images GIF en haute résolution telles que celle-ci ne seront pas animées.'''",
+       "file-no-thumb-animation": "<strong>Remarque : En raison de limitations techniques, les vignettes de ce fichier ne seront pas animées.</strong>",
+       "file-no-thumb-animation-gif": "<strong>Remarque : En raison de limitations techniques, les vignettes d'images GIF en haute résolution telles que celle-ci ne seront pas animées.</strong>",
        "newimages": "Galerie des nouveaux fichiers",
-       "imagelisttext": "Voici une liste de '''$1''' fichier{{PLURAL:$1||s}} classée $2.",
+       "imagelisttext": "Voici une liste de <strong>$1</strong> {{PLURAL:$1|fichier classé|fichiers classés}} $2.",
        "newimages-summary": "Cette page spéciale affiche les derniers fichiers importés.",
-       "newimages-legend": "Nom du fichier",
+       "newimages-legend": "Filtre",
        "newimages-label": "Nom du fichier (ou une partie de celui-ci) :",
-       "newimages-showbots": "Afficher les imports par des robots",
+       "newimages-showbots": "Afficher les imports faits par des robots",
        "newimages-hidepatrolled": "Masquer les téléchargements patrouillés",
        "noimages": "Aucune image à afficher.",
        "ilsubmit": "Rechercher",
        "saturday-at": "Samedi à $1",
        "sunday-at": "Dimanche à $1",
        "yesterday-at": "Hier à $1",
-       "bad_image_list": "Le format est le suivant :\n\nSeules les listes d’énumération (commençant par *) sont prises en compte. Le premier lien d’une ligne doit être celui d’une mauvaise image.\nLes autres liens sur la même ligne sont considérés comme des exceptions, par exemple des pages sur lesquelles l’image peut apparaître.",
+       "bad_image_list": "Le format est le suivant :\n\nSeules les listes d’énumération (commençant par *) sont prises en compte. \nLe premier lien d’une ligne doit être celui d’une mauvaise image.\nLes autres liens sur la même ligne sont considérés comme des exceptions, par exemple des pages sur lesquelles l’image peut apparaître.",
        "variantname-ku-arab": "ku-arab",
        "variantname-ku-latn": "ku-latn",
        "variantname-tg-cyrl": "tg-cyrl",
        "exif-yresolution": "Résolution verticale",
        "exif-stripoffsets": "Emplacement des données de l'image",
        "exif-rowsperstrip": "Nombre de lignes par bande",
-       "exif-stripbytecounts": "Taille en octets par bande",
+       "exif-stripbytecounts": "Taille en octets par bande compressée",
        "exif-jpeginterchangeformat": "Position du SOI JPEG",
        "exif-jpeginterchangeformatlength": "Taille en octets des données JPEG",
        "exif-whitepoint": "Chromaticité du point blanc",
        "exif-primarychromaticities": "Chromaticité des primaires",
        "exif-ycbcrcoefficients": "Coefficients YCbCr",
-       "exif-referenceblackwhite": "Valeurs de référence noir et blanc",
-       "exif-datetime": "Date de modification",
-       "exif-imagedescription": "Description de l'image",
-       "exif-make": "Fabricant de l'appareil",
-       "exif-model": "Modèle de l'appareil",
+       "exif-referenceblackwhite": "Valeurs des couples noir et blanc de référence",
+       "exif-datetime": "Date de modification du fichier",
+       "exif-imagedescription": "Titre de l'image",
+       "exif-make": "Fabricant de l'appareil photo",
+       "exif-model": "Modèle de l'appareil photo",
        "exif-software": "Logiciel utilisé",
        "exif-artist": "Auteur",
        "exif-copyright": "Détenteur du droit d'auteur",
        "exif-exifversion": "Version EXIF",
-       "exif-flashpixversion": "Version FlashPix",
+       "exif-flashpixversion": "Version FlashPix supportée",
        "exif-colorspace": "Espace colorimétrique",
        "exif-componentsconfiguration": "Signification de chaque composante",
        "exif-compressedbitsperpixel": "Mode de compression de l'image",
        "tags-edit-logentry-submit": "Appliquer les modifications à {{PLURAL:$1|cette entrée de journal|$1 entrées de journal}}",
        "tags-edit-success": "Les modifications ont été appliquées.",
        "tags-edit-failure": "Les modifications n’ont pas pu être appliquées :\n$1",
-       "tags-edit-nooldid-title": "Révision cible non valide",
-       "tags-edit-nooldid-text": "Vous n’avez soit pas spécifié de révision cible sur laquelle exécuter cette fonction, soit la révision spécifiée n’existe pas.",
+       "tags-edit-nooldid-title": "Version cible non valide",
+       "tags-edit-nooldid-text": "Vous n’avez soit pas spécifié de version cible sur laquelle exécuter cette fonction, soit la version spécifiée n’existe pas.",
        "tags-edit-none-selected": "Veuillez sélectionner au moins une balise à ajouter ou enlever.",
        "comparepages": "Comparer des pages",
        "compare-page1": "Page 1",
        "htmlform-title-not-exists": "$1 n’existe pas",
        "htmlform-user-not-exists": "<strong>$1</strong> n’existe pas.",
        "htmlform-user-not-valid": "<strong>$1</strong> n’est pas un nom d’utilisateur valide.",
-       "sqlite-has-fts": "$1 avec recherche en texte intégral supportée",
-       "sqlite-no-fts": "$1 sans recherche en texte intégral supportée",
+       "sqlite-has-fts": "$1 avec recherche en texte intégral prise en charge",
+       "sqlite-no-fts": "$1 sans recherche en texte intégral prise en charge",
        "logentry-delete-delete": "$1 {{GENDER:$2|a supprimé}} la page $3",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restauré}} la page $3",
        "logentry-delete-event": "$1 {{GENDER:$2|a modifié}} la visibilité {{PLURAL:$5|d'un événement du journal|de $5 événements du journal}} sur $3: $4",
index e6907d2..8494c41 100644 (file)
        "yourpasswordagain": "Insira o contrasinal outra vez:",
        "createacct-yourpasswordagain": "Confirme o contrasinal",
        "createacct-yourpasswordagain-ph": "Insira o contrasinal outra vez",
-       "remembermypassword": "Lembrar o meu contrasinal neste ordenador (ata $1 {{PLURAL:$1|día|días}})",
        "userlogin-remembermypassword": "Manter a miña conexión",
        "userlogin-signwithsecure": "Utilizar a conexión segura",
        "cannotloginnow-title": "Non se pode iniciar a sesión agora mesmo",
        "filerevert-submit": "Reverter",
        "filerevert-success": "Reverteuse \"'''[[Media:$1|$1]]'''\" á [$4 versión do $2 ás $3].",
        "filerevert-badversion": "Non existe unha versión local anterior deste ficheiro coa data e hora indicadas.",
+       "filerevert-identical": "A versión actual do ficheiro é igual á seleccionada.",
        "filedelete": "Borrar \"$1\"",
        "filedelete-legend": "Eliminar un ficheiro",
        "filedelete-intro": "Está a piques de eliminar o ficheiro \"'''[[Media:$1|$1]]'''\" xunto con todo o seu historial.",
index f587eac..1e8f686 100644 (file)
        "yourpasswordagain": "ગુપ્ત સંજ્ઞા (પાસવર્ડ) ફરી લખો:",
        "createacct-yourpasswordagain": "પાસવર્ડની ખાતરી કરો",
        "createacct-yourpasswordagain-ph": "પાસવર્ડ ફરીથી દાખલ કરો",
-       "remembermypassword": "આ કોમ્યૂટર પર મારી લૉગ ઇન વિગતો ધ્યાનમાં રાખો (વધુમાં વધુ $1 {{PLURAL:$1|દિવસ|દિવસ}} માટે)",
        "userlogin-remembermypassword": "મને પ્રવેશિત રાખો",
        "userlogin-signwithsecure": "સલામત જોડાણ વાપરો",
        "yourdomainname": "તમારૂં ડોમેઇન:",
index e5c20a7..9e045c3 100644 (file)
        "yourpasswordagain": "חזרה על הסיסמה:",
        "createacct-yourpasswordagain": "אימות הסיסמה",
        "createacct-yourpasswordagain-ph": "יש להקליד את הסיסמה שנית",
-       "remembermypassword": "שמירת הכניסה שלי בדפדפן הזה ({{PLURAL:$1|ליום אחד|ליומיים|ל־$1 ימים}} לכל היותר)",
        "userlogin-remembermypassword": "לזכור שנכנסתי",
        "userlogin-signwithsecure": "שימוש בחיבור מאובטח",
+       "cannotlogin-title": "לא ניתן להיכנס לחשבון",
+       "cannotlogin-text": "הכניסה לחשבון אינה אפשרית.",
        "cannotloginnow-title": "לא ניתן להיכנס עכשיו",
        "cannotloginnow-text": "הכניסה אינה אפשרית בעת שימוש ב{{GRAMMAR:תחילית|$1}}.",
+       "cannotcreateaccount-title": "לא ניתן ליצור חשבונות",
+       "cannotcreateaccount-text": "יצירת חשבונות באופן ישיר אינה מותרת באתר זה.",
        "yourdomainname": "המתחם שלך:",
        "password-change-forbidden": "אין באפשרותך לשנות סיסמאות באתר זה.",
        "externaldberror": "אירעה שגיאת אימות בבסיס הנתונים, או שאינך מורשה לעדכן את החשבון החיצוני שלך.",
        "file-thumbnail-no": "שם הקובץ מתחיל ב־<strong>$1</strong>.\nנראה שזוהי תמונה מוקטנת (ממוזערת).\nאם התמונה בגודל מלא מצויה ברשותך, יש להעלות אותה ולא את התמונה הממוזערת; אחרת, יש לשנות את שם הקובץ.",
        "fileexists-forbidden": "קובץ בשם זה כבר קיים, ואינכם יכולים להחליף אותו.\nאם אתם עדיין מעוניינים להעלות קובץ זה, אנא חזרו לדף הקודם והעלו את הקובץ תחת שם חדש.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "קובץ בשם זה כבר קיים כקובץ משותף.\nאם אתם עדיין מעוניינים להעלות קובץ זה, אנא חזרו לדף הקודם והעלו את הקובץ תחת שם חדש.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "הקובץ שהועלה הוא העתק מדויק של הגרסה הנוכחית של <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "הקובץ שהועלה הוא העתק מדויק של {{PLURAL:$2|גרסה קודמת|גרסאות קודמות}} של <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "קובץ זה זהה {{PLURAL:$1|לקובץ הבא|לקבצים הבאים}}:",
        "file-deleted-duplicate": "קובץ זהה לקובץ זה ([[:$1]]) נמחק בעבר.\nיש לבדוק את היסטוריית המחיקה של הקובץ לפני העלאתו מחדש.",
        "file-deleted-duplicate-notitle": "קובץ זהה לקובץ זה נמחק בעבר, והכותרת שלו הועלמה.\nיש לבקש ממשתמש שיכול לראות נתונים על קבצים שהועלמו לבדוק את המצב לפני העלאת הקובץ מחדש.",
        "filerevert-submit": "שחזור",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong> שוחזר ל[$4 גרסה מ־$3, $2].",
        "filerevert-badversion": "אין גרסה מקומית קודמת של הקובץ שהועלתה בתאריך המבוקש.",
+       "filerevert-identical": "הגרסה הנוכחית של הקובץ כבר זהה לגרסה שנבחרה.",
        "filedelete": "מחיקת $1",
        "filedelete-legend": "מחיקת קובץ",
        "filedelete-intro": "אתם עומדים למחוק את הקובץ <strong>[[Media:$1|$1]]</strong> יחד עם כל היסטוריית הגרסאות שלו.",
        "rollback-success": "שוחזר מעריכות של $1 לעריכה האחרונה של $2",
        "rollback-success-notify": "שוחזר מעריכות של $1 לעריכה האחרונה של $2. [$3 הצגת שינויים]",
        "sessionfailure-title": "בעיה בחיבור",
-       "sessionfailure": "נראה שיש בעיה בחיבורכם לאתר;\nפעולתכם בוטלה כאמצעי זהירות נגד התחזות לתקשורת ממחשבכם.\nאנא חזרו לדף הקודם, העלו אותו מחדש ונסו שוב.",
+       "sessionfailure": "נראה שיש בעיה בחיבור שלך לאתר;\nפעולה זו בוטלה כאמצעי זהירות נגד התחזות לתקשורת ממחשבך.\nנא לחזור לדף הקודם, לטעון אותו מחדש ולנסות שוב.",
        "changecontentmodel": "שינוי מודל התוכן של דף",
        "changecontentmodel-legend": "שינוי מודל התוכן",
        "changecontentmodel-title-label": "שם הדף",
        "pageinfo-article-id": "מזהה הדף",
        "pageinfo-language": "שפת התוכן של הדף",
        "pageinfo-content-model": "מודל התוכן של הדף",
+       "pageinfo-content-model-change": "שינוי",
        "pageinfo-robot-policy": "איסוף על־ידי רובוטים של מנועי חיפוש",
        "pageinfo-robot-index": "מותר",
        "pageinfo-robot-noindex": "אסור",
index 2dff172..cb933de 100644 (file)
        "yourpasswordagain": "कूटशब्द दुबारा लिखें:",
        "createacct-yourpasswordagain": "कूटशब्द की पुष्टि करें",
        "createacct-yourpasswordagain-ph": "कूटशब्द पुनः लिखें",
-       "remembermypassword": "इस ब्राउज़र पर मेरा लॉगिन याद रखें (अधिकतम $1 {{PLURAL:$1|दिन|दिनों}} के लिए)",
        "userlogin-remembermypassword": "मुझे लॉग्ड इन रखें",
        "userlogin-signwithsecure": "सुरक्षित कनेक्शन का प्रयोग करें",
        "cannotloginnow-title": "अभी प्रवेश नहीं हो रहा है",
        "passwordreset-emailelement": "सदस्यनाम: \n$1\n\nअस्थायी कूटशब्द: \n$2",
        "passwordreset-emailsentemail": "यदि आपका यह ईमेल आपके खाते के साथ जोड़ा गया है तो पासवर्ड बदलने का ईमेल इसमें भेज दिया गया है।",
        "passwordreset-emailsentusername": "यदि कोई ईमेल इस खाते से जुड़ी है तो पासवर्ड आपके ईमेल में भेज दिया जाएगा।",
-       "passwordreset-emailsent-capture": "नीचे दिखाया गया कूटशब्द रीसेट ई-मेल भेज दिया गया है।",
-       "passwordreset-emailerror-capture": "नीचे दृष्टित कूटशब्द रीसेट ई-मेल उत्पन्न किया गया था, परंतु उसे {{GENDER:$2|सदस्य}} को भेजना असफल रहा।\nत्रुटि: $1",
        "passwordreset-emailerror-capture2": "{{GENDER:$2|सदस्य}} को ईमेल भेजना विफल : $1 {{PLURAL:$3|सदस्य नाम और पासवर्ड|सदस्य नाम और पासवर्ड की सूची}} नीचे दिया गया है।",
        "passwordreset-invalideamil": "अवैध ईमेल पता",
        "changeemail": "ई-मेल पता परिवर्तित करें",
        "changeemail-header": "अपना ईमेल पता परिवर्तन हेतु इसे पूरा करें। यदि आप अपना वर्तमान ईमेल पता हटाना चाहते हैं, तो इसे खाली छोड़ दें और इसे भेजें।",
-       "changeemail-passwordrequired": "आपको इस परिवर्तन हेतु पासवर्ड (कूटशब्द) डालना होगा।",
        "changeemail-no-info": "इस पृष्ठ का सीधे प्रयोग करने के लिए आपको लॉग इन करना होगा।",
        "changeemail-oldemail": "वर्तमान ई-मेल पता:",
        "changeemail-newemail": "नया ई-मेल पता:",
        "minoredit": "यह एक छोटा बदलाव है",
        "watchthis": "इस पृष्ठ को ध्यानसूची में डालें",
        "savearticle": "पृष्ठ सहेजें",
+       "savechanges": "बदलाव सहेजें",
        "publishpage": "पृष्ठ प्रकाशित करें",
+       "publishchanges": "परिवर्तन प्रकाशित करें",
        "preview": "झलक",
        "showpreview": "झलक दिखाएँ",
        "showdiff": "बदलाव दिखाएँ",
        "undo-nochange": "ऐसा लगता है कि इस सम्पादन को पहले ही पूर्ववत कर दिया गया है।",
        "undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|वार्ता]]) द्वारा किए बदलाव $1 को पूर्ववत किया",
        "undo-summary-username-hidden": "छुपाए गए सदस्य द्वारा किये बदलाव $1 को पूर्ववत किया",
-       "cantcreateaccounttitle": "खाता खोल नहीं सकते",
        "cantcreateaccount-text": "इस आइ॰पी पते ('''$1''') को खाता निर्मित करने से [[User:$3|$3]] ने प्रतिबंधित किया है।\n\nइसके लिये $3 ने ''$2'' कारण दिया है।",
        "cantcreateaccount-range-text": "<strong>$1</strong> की श्रेणी में आने वाले आई॰पी पतों से, जिसमें आपका आई॰पी पता (<strong>$4</strong>) शामिल है, नए खातों की रचना [[User:$3|$3]] द्वारा अवरोधित की गयी है। \n\n$3 द्वारा दिया गया कारण है: \"$2\"",
        "viewpagelogs": "इस पृष्ठ का लॉग देखें",
        "mw-widgets-dateinput-placeholder-day": "DD-MM-YYYY",
        "mw-widgets-titleinput-description-new-page": "पृष्ठ अभी मौजूद नहीं है",
        "mw-widgets-titleinput-description-redirect": "$1 को अनुप्रेषित",
-       "api-error-blacklisted": "कृपया कोई दूसरा विवरणात्मक शीर्षक चुनें।",
        "sessionmanager-tie": "एक साथ कई अनुरोध को नहीं मिला सकता: $1",
        "sessionprovider-generic": "$1 सत्र",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "कुकी-आधारित सत्र",
index db69923..d8780e1 100644 (file)
        "yourpasswordagain": "Jelszavad ismét:",
        "createacct-yourpasswordagain": "Új jelszó megerősítése",
        "createacct-yourpasswordagain-ph": "Írd be a jelszót újra",
-       "remembermypassword": "Emlékezzen rám ezen a számítógépen (legfeljebb $1 napig)",
        "userlogin-remembermypassword": "Maradjak bejelentkezve",
        "userlogin-signwithsecure": "Biztonságos kapcsolat használata",
+       "cannotlogin-title": "A bejelentkezés nem lehetséges.",
+       "cannotlogin-text": "A bejelentkezés nem lehetséges.",
        "cannotloginnow-title": "Nem lehet most bejelentkezni",
        "cannotloginnow-text": "A bejelentkezés nem lehetséges $1 használatakor.",
+       "cannotcreateaccount-title": "Felhasználói fiók létrehozása sikertelen",
+       "cannotcreateaccount-text": "Közvetlen fiók létrehozása nem engedélyezett ezen a wikin.",
        "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.",
        "pageinfo-article-id": "Lapazonosító",
        "pageinfo-language": "Laptartalom nyelve",
        "pageinfo-content-model": "Oldal tartalom modell",
+       "pageinfo-content-model-change": "módosítás",
        "pageinfo-robot-policy": "Indexelés robottal",
        "pageinfo-robot-index": "Engedélyezett",
        "pageinfo-robot-noindex": "Nem engedélyezett",
index a94a60a..20f83e5 100644 (file)
        "yourpasswordagain": "Կրկնեք գաղտնաբառը",
        "createacct-yourpasswordagain": "Հաստատեք գաղտնաբառը",
        "createacct-yourpasswordagain-ph": "Կրկին մուտքագրեք գաղտնաբառը",
-       "remembermypassword": "Հիշել իմ մուտքը այս դիտարկչում ($1 {{PLURAL:$1|օրից}} ոչ ավել ժամկետով)",
        "userlogin-remembermypassword": "Մուտք գործած մնալ",
        "userlogin-signwithsecure": "Օգտագործել անվտանգ միացում",
        "cannotloginnow-title": "Այժմ դուրս գալ անհնար է",
        "passwordreset-emailtitle": "{{SITENAME}} հաշվի մանրամասները",
        "passwordreset-emailelement": "Մասնակցային անունը՝ \n$1\n\nԺամանակավոր գաղտնաբառը՝ \n$2",
        "passwordreset-emailsentemail": "Ուղարկվեց հիշեցնող էլ․ նամակ։",
-       "passwordreset-emailsent-capture": "Ուղարկվեց հիշեցնող էլ․ նամակ։ Այն ներկայացված է ստորև։",
-       "passwordreset-emailerror-capture": "Ուղարկվեց հիշեցնող էլ․ նամակ։ Այն ներկայացված է ստորև։ Սակայն մասնակցին ուղարկելը չհաջողվեց․",
        "changeemail": "Փոխել էլ. հասցեն",
        "changeemail-header": "Փոխել հաշվի էլ․ հասցեն",
        "changeemail-oldemail": "Ներկա էլ․ հասցե․",
        "minoredit": "Սա չնչին խմբագրում է",
        "watchthis": "Հսկել այս էջը",
        "savearticle": "Հիշել էջը",
+       "savechanges": "Պահպանել փոփոխությունները",
+       "publishpage": "Հիշել փոփոխությունները",
+       "publishchanges": "Հիշել փոփոխությունները",
        "preview": "Նախադիտում",
        "showpreview": "Նախադիտել",
        "showdiff": "Կատարված փոփոխությունները",
        "undo-success": "Խմբագրումը կարող է հետ շրջվել։ Ստուգեք տարբերակների համեմատությունը ստորև, որպեսզի համոզվեք, որ դա է ձեզ հետաքրքրող փոփոխությունը և մատնահարեք «Հիշել էջը»՝ գործողությունն ավարտելու համար։",
        "undo-failure": "Խմբագրումը չի կարող հետ շրջվել միջանկյալ խմբագրումների ընդհարման պատճառով։",
        "undo-summary": "Հետ է շրջվում $1 խմբագրումը, որի հեղինակն է՝ [[Special:Contributions/$2|$2]] ([[User talk:$2|քննարկում]]) {{GENDER:$2|մասնակիցը|մասնակցուհին}}",
-       "cantcreateaccounttitle": "Չհաջողվեց ստեղծել մասնակցային հաշիվ",
        "cantcreateaccount-text": "Այս IP-հասցեից ('''$1''') մասնակցային հաշվի ստեղծումը արգելափակվել է [[User:$3|$3]] մասնակցի կողմից։\n\n$3 մասնակիցը տվել է հետևյալ պատճառը. ''$2''",
        "viewpagelogs": "Դիտել այս էջի տեղեկամատյանները",
        "nohistory": "Այս էջը չունի խմբագրումների պատմություն։",
index bc271bd..2d8d4ef 100644 (file)
        "yourpasswordagain": "Ulangi kata sandi:",
        "createacct-yourpasswordagain": "Konfirmasi kata sandi",
        "createacct-yourpasswordagain-ph": "Masukkan lagi kata sandi",
-       "remembermypassword": "Ingat kata sandi saya di komputer ini (selama $1 {{PLURAL:$1|hari|hari}})",
        "userlogin-remembermypassword": "Biarkan saya tetap masuk",
        "userlogin-signwithsecure": "Gunakan server aman",
        "cannotloginnow-title": "Tidak dapat masuk log saat ini",
        "minoredit": "Ini adalah suntingan kecil.",
        "watchthis": "Pantau halaman ini",
        "savearticle": "Simpan halaman",
+       "savechanges": "Simpan perubahan",
        "publishpage": "Terbitkan halaman",
        "preview": "Pratayang",
        "showpreview": "Lihat pratayang",
index a005dcc..6184149 100644 (file)
        "yourpasswordagain": "Imakinilya manen ti kontrasenias:",
        "createacct-yourpasswordagain": "Pasingkedan ti kontrasenias",
        "createacct-yourpasswordagain-ph": "Ikabil manen ti kontrasenias",
-       "remembermypassword": "Laglagipem ti iseserrekko iti daytoy a pagbasabasa (para iti kapaut iti $1 {{PLURAL:$1|nga aldaw|nga al-aldaw}})",
        "userlogin-remembermypassword": "Taginayonennak nga iserrek",
        "userlogin-signwithsecure": "Usaren ti natalged a koneksion",
        "cannotloginnow-title": "Saan a mabalin itan iti sumrek",
        "publishchanges": "Ipablaak dagiti binaliwan",
        "preview": "Ipadas",
        "showpreview": "Ipakita ti ipadas",
-       "showdiff": "Ipakita dagiti sinukatan",
+       "showdiff": "Ipakita dagiti binaliwan",
        "blankarticle": "<strong>Ballaag:</strong> Ti panid a parpatuatem ket blanko.\nNo pindutem manen ti \"{{int:savearticle}}\", ti panid ket mapartuatto nga awan ti ania man a linaon.",
        "anoneditwarning": "<strong>Ballaag:</strong> Saanka a nakastrek. Ti IP a pagtaengan ket publikonto a makita nga agaramidka iti ania man a panagurnos. No <strong>[$1 sumrekka]</strong> wenno <strong>[$2 agpartuatka iti pakabilangan]</strong>, dagiti inurnosmo ket maitunosto iti naganmo nga agar-aramat, ken dagiti dadduma pay a pagimbagan.",
        "anonpreviewwarning": "<em>Saanka a nakastrek. Ti panagidulin ket agirehistro ti IP a pagtaengam kadagitoy a pakasaritaan ti panagurnos iti daytoy a panid.</em>",
        "tooltip-ca-nstab-template": "Kitaen ti plantilia",
        "tooltip-ca-nstab-help": "Kitaen ti panid ti tulong",
        "tooltip-ca-nstab-category": "Kitaen ti panid ti kategoria",
-       "tooltip-minoredit": "Markaan daytoy a kas bassit a panag-urnos",
-       "tooltip-save": "Idulin dagiti sinukatam",
+       "tooltip-minoredit": "Markaan daytoy a kas bassit a panagurnos",
+       "tooltip-save": "Idulin dagiti binaliwam",
        "tooltip-publish": "Ipablaak dagiti binaliwam",
-       "tooltip-preview": "Ipadas dagiti sinukatam, pangngaasi nga usarem daytoy sakbay nga idulin ti panid!",
-       "tooltip-diff": "Ipakita no ania dagiti sinukatan nga inaramidmo iti testo",
+       "tooltip-preview": "Ipadas dagiti binaliwam. Pangngaasi nga usaren daytoy sakbay nga idulin ti panid.",
+       "tooltip-diff": "Ipakita no ania dagiti binaliwan nga inaramidmo iti teksto",
        "tooltip-compareselectedversions": "Kitaen ti naggidiatan dagiti dua a napili a bersion iti daytoy a panid.",
        "tooltip-watch": "Inayon daytoy a panid iti listaan ti bambantayam",
        "tooltip-watchlistedit-normal-submit": "Ikkaten dagiti titulo",
index c4788aa..41aa59c 100644 (file)
        "yourname": "Vua uzantonomo:",
        "yourpassword": "Pasovorto:",
        "yourpasswordagain": "Riskribez la pasovorto:",
-       "remembermypassword": "Memorez mea pasovorto en ca komputoro (maximo: $1 {{PLURAL:$1|dio|dii}})",
        "yourdomainname": "Vua domano:",
        "login": "Enirar",
        "nav-login-createaccount": "Enirar",
index a8fbc04..fd1d4c4 100644 (file)
@@ -42,6 +42,7 @@
        "tog-watchdefault": "Bæta síðum og skrám sem ég breyti á vaktlistann minn",
        "tog-watchmoves": "Bæta á vaktlistann minn síðum og skrám sem ég færi",
        "tog-watchdeletion": "Bæta síðum og skrám sem ég eyði á vaktlistann minn",
+       "tog-watchuploads": "Bæta nýjum skrám sem ég hleð inn við á vaktlistann minn",
        "tog-watchrollback": "Bæta síðum þar sem ég hef tekið aftur breytingu á vaktlistann minn",
        "tog-minordefault": "Merkja sjálfgefið allar breytingar sem minniháttar",
        "tog-previewontop": "Sýna forskoðun á undan breytingareitnum",
        "yourpasswordagain": "Endurrita lykilorð:",
        "createacct-yourpasswordagain": "Staðfestu lykilorðið",
        "createacct-yourpasswordagain-ph": "Sláðu inn lykilorðið aftur",
-       "remembermypassword": "Muna innskráninguna mína í þessum vafra (í allt að $1 {{PLURAL:$1|dag|daga}})",
        "userlogin-remembermypassword": "Muna innskráningu mína",
        "userlogin-signwithsecure": "Nota örugga tengingu",
        "cannotloginnow-title": "Get ekki skráð inn núna",
        "minoredit": "Þetta er minniháttar breyting",
        "watchthis": "Vakta þessa síðu",
        "savearticle": "Vista síðu",
+       "savechanges": "Vista breytingar",
        "publishpage": "Gefa út síðu",
        "publishchanges": "Gefa út breytingar",
        "preview": "Forskoða",
index 184f0cb..76dd0f0 100644 (file)
        "yourpasswordagain": "Ripeti la password:",
        "createacct-yourpasswordagain": "Conferma password",
        "createacct-yourpasswordagain-ph": "Inserisci nuovamente la password",
-       "remembermypassword": "Ricorda la password su questo browser (per un massimo di $1 {{PLURAL:$1|giorno|giorni}})",
        "userlogin-remembermypassword": "Mantienimi collegato",
        "userlogin-signwithsecure": "Usa una connessione sicura",
        "cannotloginnow-title": "Impossibile accedere ora",
        "file-thumbnail-no": "Il nome del file inizia con <strong>$1</strong>; sembra quindi essere una miniatura ''(thumbnail)''.\nSe si dispone dell'immagine nella risoluzione originale, si prega di caricarla. In caso contrario, si prega di cambiare il nome del file.",
        "fileexists-forbidden": "Un file con questo nome esiste già e non può essere sovrascritto. Tornare indietro e modificare il nome con il quale caricare il file. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Un file con questo nome esiste già nell'archivio di risorse multimediali condivise. Se si desidera ancora caricare il file, tornare indietro e modificare il nome con il quale caricare il file. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Il file caricato è un duplicato esatto dell'attuale versione di <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Il file caricato è un duplicato esatto di {{PLURAL:$2|una versione precedente|versioni precedenti}} di <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Questo file è un duplicato {{PLURAL:$1|del seguente|dei seguenti}} file:",
        "file-deleted-duplicate": "Un file identico a questo ([[:$1]]) è stato cancellato in passato. Verificare la cronologia delle cancellazioni prima di caricarlo di nuovo.",
        "file-deleted-duplicate-notitle": "Un file identico a questo è stato cancellato in passato, ed il titolo è stato soppresso. Chiedi a qualcuno che ha la possibilità di vedere i file soppressi di esaminare la situazione prima di procedere nuovamente al caricamento.",
        "filerevert-submit": "Ripristina",
        "filerevert-success": "'''Il file [[Media:$1|$1]]''' è stato ripristinato alla [$4 versione del $2, $3].",
        "filerevert-badversion": "Non esistono versioni locali precedenti del file con il timestamp richiesto.",
+       "filerevert-identical": "La versione attuale del file è già identica a quella selezionata.",
        "filedelete": "Cancella $1",
        "filedelete-legend": "Cancella il file",
        "filedelete-intro": "Stai per cancellare il file '''[[Media:$1|$1]]''' con tutta la sua cronologia.",
        "pageinfo-article-id": "ID della pagina",
        "pageinfo-language": "Lingua del contenuto della pagina",
        "pageinfo-content-model": "Modello del contenuto della pagina",
+       "pageinfo-content-model-change": "cambia",
        "pageinfo-robot-policy": "Indicizzazione per i robot",
        "pageinfo-robot-index": "Consentito",
        "pageinfo-robot-noindex": "Non consentito",
index 6b2599e..5f451b9 100644 (file)
@@ -71,7 +71,8 @@
                        "Kana Higashikawa",
                        "Shield-9",
                        "Waiesu",
-                       "Matma Rex"
+                       "Matma Rex",
+                       "組曲師"
                ]
        },
        "tog-underline": "リンクの下線:",
        "yourpasswordagain": "パスワード再入力:",
        "createacct-yourpasswordagain": "パスワード再入力",
        "createacct-yourpasswordagain-ph": "パスワードを再入力",
-       "remembermypassword": "このブラウザーにログイン情報を保存 (最長 $1 {{PLURAL:$1|日|日間}})",
        "userlogin-remembermypassword": "ログイン状態を保持",
        "userlogin-signwithsecure": "安全な接続の使用",
        "cannotloginnow-title": "今はログインできません",
        "feedback-terms": "私のユーザーエージェント情報には、使用ブラウザやオペレーティングシステムのバージョンの情報が含まれており、その情報は私が提供するフィードバックとあわせて公開されることを理解しました。",
        "feedback-termsofuse": "利用規約に従い、フィードバックを提供することに同意します。",
        "feedback-thanks": "ありがとうございます。フィードバックを「[$2 $1]」のページに投稿しました。",
-       "feedback-thanks-title": "ã\81\82ã\82\8aã\81\8cã\81¨ã\81\86ã\81\94ã\81\96ã\81\84ã\81¾ã\81\99!",
+       "feedback-thanks-title": "ã\81\8aé¡\98ã\81\84ã\81\97ã\81¾ã\81\99ï¼\81",
        "feedback-useragent": "ユーザーエージェント:",
        "searchsuggest-search": "検索",
        "searchsuggest-containing": "この語句を全文検索",
index 71603bd..d3f7f25 100644 (file)
@@ -35,7 +35,7 @@
        "tog-watchmoves": "Wuwuh kaca lan barkas lih-lihanku nyang pawawanganku",
        "tog-watchdeletion": "Wuwuh kaca lan barkas busakanku nyang pawawanganku",
        "tog-watchuploads": "Wuwuh barkas anyar unggahanku nyang pawawanganku",
-       "tog-watchrollback": "Wuwuh kaca sing tak wurungaké nyang pawawanganku",
+       "tog-watchrollback": "Wuwuh kaca sing takpulihaké nyang pawawanganku",
        "tog-minordefault": "Tengeri kabèh besutan minangka besutan cilik sacara baku",
        "tog-previewontop": "Deleng pratuduh sadurungé mbesut kothak",
        "tog-previewonfirst": "Delelng pratuduh nalika mbesut pisanan",
        "about": "Bab",
        "article": "Kaca isi",
        "newwindow": "(buka mawa jendhéla anyar)",
-       "cancel": "Wurungaké",
+       "cancel": "Wurung",
        "moredotdotdot": "Liyané...",
        "morenotlisted": "Pratélan iki ora jangkep.",
        "mypage": "Kaca",
        "viewyourtext": "Sampéyan bisa ndeleng lan nyalin sumbering <strong>besutaning sampéyan</strong> ing kaca iki.",
        "protectedinterface": "Kaca iki isiné tèks antarmuka sing dienggo software lan wis dikunci kanggo menghindari kasalahan.",
        "editinginterface": "'''Pènget:''' Panjenengan nyunting kaca sing dianggo nyedyakaké tèks antarmuka kanggo piranti alus.\nPangowahan kaca iki bakal awèh pangaruh marang tampilan antarmuka panganggo kanggoné panganggo liya.\nKanggo terjemahan, mangga nganggo [https://translatewiki.net/wiki/Main_Page?setlang=en translatewiki.net], proyèk lokalisasi MediaWiki.",
-       "translateinterface": "Kanggo nambah utawa ngowah pertalan kanggo kabèh wiki, mangga anggoa [https://translatewiki.net/ translatewiki.net] minangka proyèk palokaling MediaWiki.",
+       "translateinterface": "Saperlu nambah utawa ngowah pertalan tumrap kabèh wiki, mangga anggoa [https://translatewiki.net/ translatewiki.net] minangka proyèk panglokaling MediaWiki.",
        "cascadeprotected": "Kaca iki wis direksa saka panyuntingan amerga disertakaké ing {{PLURAL:$1|kaca|kaca-kaca}} ngisor iki sing wis direksa mawa opsi \"runtun\" diaktifaké:\n$2",
        "namespaceprotected": "Panjenengan ora kagungan idin kanggo nyunting kaca ing bilik nama '''$1'''.",
        "customcssprotected": "Sampéyan ora dililakaké nyunting kaca CSS iki amarga kaisi pangaturan pribadi saka panganggo liya.",
        "yourpasswordagain": "Tik manèh tembung wadiné:",
        "createacct-yourpasswordagain": "Netepaké tembung wadi",
        "createacct-yourpasswordagain-ph": "Lebokaké manèh tembung wadiné",
-       "remembermypassword": "Émut tembung sandi kula (salebeting $1 {{PLURAL:$1|dinten|dinten}})",
        "userlogin-remembermypassword": "Gawé amrih aku panggah kalebu",
        "userlogin-signwithsecure": "Nganggo koneksi aman",
        "cannotloginnow-title": "Ora bisa mlebu saiki",
        "cannotchangeemail": "Alamat layang èlèktronik akun ora bisa diganti nèng wiki iki.",
        "emaildisabled": "Situs iki ora bisa ngirim layang èlèktronik.",
        "accountcreated": "Akun wis kagawé",
-       "accountcreatedtext": "Akun panganggo kanggo  [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|wicara]]) wis digawé.",
+       "accountcreatedtext": "Akun panganggo [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|rembug]]) wis digawé.",
        "createaccount-title": "Gawé rékening kanggo {{SITENAME}}",
        "createaccount-text": "Ana wong sing nggawé sawijining akun utawa rékening kanggo alamat e-mail panjenengan ing {{SITENAME}} ($4) mawa jeneng \"$2\" lan tembung sandi \"$3\". Panjenengan disaranaké kanggo mlebu log lan ngganti tembung sandi panjenengan saiki.\n\nPanjenengan bisa nglirwakaké pesen iki yèn akun utawa rékening iki digawé déné sawijining kaluputan.",
        "login-throttled": "Panjenengan wis kakèhan njajal mlebu log.\nTulung nunggu dhisik $1 sadurungé njajal manèh.",
        "minoredit": "Iki besutan cilik",
        "watchthis": "Awasi kaca iki",
        "savearticle": "Simpen kaca",
+       "savechanges": "Simpen owahan",
        "publishpage": "Babar kaca",
        "publishchanges": "Babar owahan",
        "preview": "Pratuduh",
        "page_last": "pungkasan",
        "histlegend": "Kanggo mbandhingaké: tandhani kothak radhio révisi-révisi sing arep dibandhingaké lan pencèt ''Enter'' utawa tombol sing ana ing ngisor.<br />\nLegéndha: <strong>({{int:cur}})</strong> = béda karo révisi pungkasan, <strong>({{int:last}})</strong> = béda karo révisi sadurungé, <strong>{{int:minoreditletter}}</strong> = besutan cilik.",
        "history-fieldset-title": "Luru sujarah",
-       "history-show-deleted": "Namung sing dibusak",
-       "histfirst": "suwé dhéwé",
+       "history-show-deleted": "Mligi sing dibusak",
+       "histfirst": "lawas dhéwé",
        "histlast": "anyar dhéwé",
        "historysize": "($1 {{PLURAL:$1|bét|bét}})",
        "historyempty": "(suwung)",
        "lineno": "Larik $1:",
        "compareselectedversions": "Bandhingna vèrsi kapilih",
        "showhideselectedversions": "Tampilaké/dhelikaké révisi kapilih",
-       "editundo": "wurungaké",
+       "editundo": "wurung",
        "diff-empty": "(Ora ana bedane)",
        "diff-multi-sameuser": "({{PLURAL:$1|Saowahan madya|$1 owahan madya}} déning panganggo sing padha ora dituduhaké)",
        "diff-multi-manyusers": "({{PLURAL:$1Siji rèvisi sedhengan|$1 rèvisi sedhengan}} déning luwih saka $2 {{PLURAL:$2|panganggo|panganggo}} ora dituduhaké)",
        "searchmenu-exists": "'''Ana kaca kanthi jeneng \"[[$1]]\" ing wiki iki'''",
        "searchmenu-new": "<strong>Gawéa kaca \"[[:$1]]\" nyang wiki iki!</strong> {{PLURAL:$2|0=|Uga delenga kaca sing katemu sarana panggolèking sampéyan.|Uga delenga kasiling panggolèk.}}",
        "searchprofile-articles": "Kaca isi",
-       "searchprofile-images": "Sarwamadya",
+       "searchprofile-images": "Multimédhia",
        "searchprofile-everything": "Samubarang",
        "searchprofile-advanced": "Lungidan",
        "searchprofile-articles-tooltip": "Golèkan ing $1",
        "yourvariant": "Werna basa isi:",
        "prefs-help-variant": "Varian utawa ortograpi sing Sampéyan pilih kanggo nampilaké kaca kontèn saka wiki iki.",
        "yournick": "Asma sesinglon/samaran (kagem tapak asta):",
-       "prefs-help-signature": "Komentar ing kaca wicara kudu ditapak astani nganggo \"<nowiki>~~~~</nowiki>\" sing bakal dikonvèrsi dadi tapak asta panjenengan lan tanggal wektu.",
+       "prefs-help-signature": "Tanggapan ing kaca parembugan kudu ditandhatangani mawa \"<nowiki>~~~~</nowiki>\", sing bakal salin dadi tandha tangan lan cap wektumu.",
        "badsig": "Tapak astanipun klèntu; cèk rambu HTML.",
        "badsiglength": "Tapak asta panjenengan kedawan.\nAja luwih saka {{PLURAL:$1|karakter|karakter}}.",
        "yourgender": "Kepiyé sampéyan medhar priangganing sampéyan?",
        "grant-delete": "Busak kaca, owahan, lan isian cathetan",
        "newuserlogpage": "Log naraguna anyar",
        "newuserlogpagetext": "Ing ngisor iki kapacak log pandaftaran panganggo anyar.",
-       "rightslog": "Log pangowahan hak aksès",
+       "rightslog": "Log hak panganggo",
        "rightslogtext": "Ing ngisor iki kapacak log pangowahan marang hak-hak panganggo.",
        "action-read": "maca kaca iki",
        "action-edit": "besut kaca iki",
        "action-createpage": "nggawé kaca-kaca",
-       "action-createtalk": "gawé kaca wicara anyar",
+       "action-createtalk": "gawé kaca parembugan iki",
        "action-createaccount": "gawé akun panganggo iki",
        "action-minoredit": "tandhani iki minangka besutan cilik",
        "action-move": "alihna kaca iki",
        "statistics-header-hooks": "Statistik liya",
        "statistics-articles": "Kaca-kaca isi",
        "statistics-pages": "Gunggung kaca",
-       "statistics-pages-desc": "Kabèh kaca ing wiki iki, klebu kaca wicara, pangalihan, lan liya-liyané.",
+       "statistics-pages-desc": "Kabèh kaca ing wiki iki, kalebu kaca parembugan, alihan, lsp.",
        "statistics-files": "Berkas sing diunggahaké",
        "statistics-edits": "Gunggung suntingan wiwit {{SITENAME}} diwiwiti",
        "statistics-edits-average": "Rata-rata suntingan saben kaca",
        "usercreated": "{{GENDER:$3|Digawé}} $1 wanci $2",
        "newpages": "Kaca anyar",
        "newpages-username": "Asma panganggo:",
-       "ancientpages": "Kaca-kaca langkung sepuh",
+       "ancientpages": "Kaca paling lawas",
        "move": "Pindhahen",
        "movethispage": "Lih kaca iki",
        "unusedimagestext": "Berkas-berkas sing kapacak iki ana nanging ora dienggo ing kaca apa waé.\nTulung digatèkaké yèn situs wèb liyané mbok-menawa bisa nyambung ing sawijining berkas sacara langsung mawa URL langsung, lan berkas-berkas kaya mengkéné iku mbok-menawa ana ing daftar iki senadyan ora dienggo aktif manèh.",
        "categories": "Kategori",
        "categoriespagetext": "{{PLURAL:$1|kategori ing ngisor iki ngandhut|kategori ing ngisor iki ngandhut}} kaca utawa media.\n[[Special:UnusedCategories|Kategori sing ora dianggo]] ora ditampilaké ing kéné.\nDeleng uga [[Special:WantedCategories|kategori sing diperlokaké]].",
        "categoriesfrom": "Tampilaké kategori-kategori diwiwiti saka:",
-       "deletedcontributions": "Sumbanganing panganggo sing dibusak",
+       "deletedcontributions": "Sumbangan panganggo sing dibusak",
        "deletedcontributions-title": "Sumbanganing panganggo sing dibusak",
        "sp-deletedcontributions-contribs": "sumbangan",
        "linksearch": "Golèkan pranala njaba",
        "delete-toobig": "Kaca iki ndarbèni sajarah panyuntingan sing dawa, yaiku ngluwihi $1 {{PLURAL:$1|revision|révisi}}.\nPambusakan kaca sing kaya mangkono mau wis ora diparengaké kanggo menggak anané karusakan ing {{SITENAME}}.",
        "delete-warning-toobig": "Kaca iki duwé sajarah panyuntingan sing dawa, luwih saka $1 {{PLURAL:$1|révisi|révisi}}.\nMbusak kaca iki bisa ngrusak operasi basis data ing {{SITENAME}};\nkudu ngati-ati.",
        "deleting-backlinks-warning": "'''Awas:''' Kaca liyane mungkin ana sing nautake ing kaca sing arep sampeyan busak.",
-       "rollback": "Wurungaké besutan",
+       "rollback": "Pulihaké besutan",
        "rollbacklink": "balèkaké",
        "rollbacklinkcount": "balèkaké $1 {{PLURAL:$1|besutan|besutan}}",
        "rollbacklinkcount-morethan": "balèkaké luwih saka $1 {{PLURAL:$1|suntingan|suntingan}}",
        "rollbackfailed": "Pambalèkan gagal dilakoni",
        "cantrollback": "Ora bisa mbalèkaké suntingan; panganggo pungkasan iku siji-sijiné penulis artikel iki.",
-       "alreadyrolled": "Ora bisa mbalèkaké suntingan pungkasan [[:$1]] déning [[User:$2|$2]] ([[User talk:$2|Wicara]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); wong liya wis nyunting utawa mbalèkaké kaca artikel iku.\n\nSuntingan pungkasan dilakoni déning [[User:$3|$3]] ([[User talk:$3|Wicara]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
+       "alreadyrolled": "Ora bisa mulihaké besutan pungkasan [[:$1]] déning [[User:$2|$2]] ([[User talk:$2|rembug]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); ana wong liya sing wis mbesut utawa mulihaké kaca iki.\n\nBesutan pungkasan kaca iku garapané [[User:$3|$3]] ([[User talk:$3|rembug]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Ringkesan suntingan yaiku: <em>$1</em>.",
        "revertpage": "Besutan sing dibalèkaké [[Special:Contributions/$2|$2]] ([[User talk:$2|rembugan]]) bab owahan pungkasan déning [[User:$1|$1]]",
        "revertpage-nouser": "Suntingan déning panganggo sing didhelikake, dibalèkaké nèng benahan pungkasan déning [[User:$1|$1]]",
        "tooltip-namespace_association": "Centhang kothak iki kanggo nglebokaké uga bilik jeneng gumenan utawa subyèk sing kakait karo bilik jeneng kapilih",
        "blanknamespace": "(Pokok)",
        "contributions": "Sumbangan {{GENDER:$1|panganggo}}",
-       "contributions-title": "Sumbanganing panganggo $1",
+       "contributions-title": "Sumbangan panganggo $1",
        "mycontris": "Sumbangan",
        "anoncontribs": "Sumbangan",
        "contribsub2": "Kanggo {{GENDER:$3|$1}} ($2)",
        "sp-contributions-deleted": "sumbanganing panganggo sing dibusak",
        "sp-contributions-uploads": "unggahan",
        "sp-contributions-logs": "log",
-       "sp-contributions-talk": "wicara",
+       "sp-contributions-talk": "rembug",
        "sp-contributions-userrights": "pengaturan hak panganggo",
        "sp-contributions-blocked-notice": "Panganggo iki lagi diblokir.\nÈntri log blokiran pungkasan sumadhiya nèng ngisor kanggo rujukan:",
        "sp-contributions-blocked-notice-anon": "Alamat IP iki lagi diblokir.\nÈntri log blokiran pungkasan sumadhiya nèng ngisor kanggo rujukan:",
        "sp-contributions-search": "Golèk sumbangan",
        "sp-contributions-username": "Alamat IP utawa jeneng panganggo:",
-       "sp-contributions-toponly": "Tuduhaké was suntingan saka benahan pungkasan",
+       "sp-contributions-toponly": "Tuduhaké besutan mligi rèvisi anyar",
+       "sp-contributions-newonly": "Tuduhaké besutan mligi kaca gawéan",
        "sp-contributions-submit": "Golèk",
        "whatlinkshere": "Sing nggayut mréné",
        "whatlinkshere-title": "Kaca mawa pranala nggayut \"$1\"",
        "tooltip-recreate": "Gawéa kaca iki manèh senadyan tau dibusak",
        "tooltip-upload": "Miwiti pangunggahan",
        "tooltip-rollback": "Balèkaké besutan-besutan kaca iki déning sing pungkasan nyumbang sarana saklikan.",
-       "tooltip-undo": "\"Wurungaké\" mbalèkaké besutan iki lan mbukak blangko besutan sarana modhe pratuduh. Alesan kena diwuwuhaké ing babagan ringkesan.",
+       "tooltip-undo": "\"Wurung\" mbalèkaké besutan iki lan mbukak blangko besutan sarana modhe pratuduh. Alesan kena diwuwuhaké ing babagan ringkesan.",
        "tooltip-preferences-save": "Simpen préperensi",
        "tooltip-summary": "Isi tingkesan cendhak",
        "anonymous": "{{PLURAL:$1|Panganggo|panganggo}} anon ing {{SITENAME}}.",
        "logentry-newusers-create2": "Akun panganggo $3 {{GENDER:$2|digawé}} déning $1",
        "logentry-newusers-byemail": "Akun panganggo $3 {{GENDER:$2|digawé}} déning $1 lan tembung sandhine dikirim lewat layang elektronik",
        "logentry-newusers-autocreate": "Akun $1 {{GENDER:$2|digawé}} otomatis",
+       "logentry-protect-unprotect": "$1 {{GENDER:$2|ngilangi}} rereksan saka $3",
        "logentry-rights-rights": "$1 {{GENDER:$2|ngganti}} golongané {{GENDER:$6|$3}} saka $4 dadi $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|ngganti}} golongané $3",
        "logentry-rights-autopromote": "$1 otomatis {{GENDER:$2|dipromosikne}} saka $4 nèng $5",
index 0037459..e9e2f92 100644 (file)
        "yourpasswordagain": "ხელმეორედ შეიყვანეთ პაროლი",
        "createacct-yourpasswordagain": "დაადასტურეთ პაროლი",
        "createacct-yourpasswordagain-ph": "ხელმეორედ შეიყვანეთ პაროლი",
-       "remembermypassword": "დამიმახსოვრე ამ კომპიუტერზე (მაქსიმუმ $1 {{PLURAL:$1|დღე}})",
        "userlogin-remembermypassword": "დამიმახსოვრე",
        "userlogin-signwithsecure": "უსაფრთხო კავშირის გამოყენება",
        "cannotloginnow-title": "ამჟამად შესვლა შუეძლებელია",
        "right-managechangetags": "[[Special:Tags|ტეგების]] შექმნა და (დე)აქტივაცია",
        "right-applychangetags": "[[Special:Tags|tags]] მიღება თქვენ ცვლილებებთან ერთად",
        "right-changetags": "თვითნებური [[Special:Tags|tags]] დამატება ან წაშლა ცალკეულ ცვლილებებსა და ჟურნალის ჩანაწერებში",
+       "right-deletechangetags": "მონაცემთა ბაზიდან [[Special:Tags|ტეგების]] წაშლა",
        "grant-generic": "\"$1\" უფლებები",
        "grant-group-page-interaction": "კავშირი გვერდებთან",
        "grant-group-file-interaction": "კავშირი მედია-ფაილებთან",
        "action-managechangetags": "ტეგების შექმნა და (დე)აქტივაცია",
        "action-applychangetags": "ტეგების მიღება თქვენ ცვლილებებთან ერთად",
        "action-changetags": "თავისუფალი ტეგების დამატება და წაშლა ცალკეულ ცვლილებებსა და ჟურნალების ჩანაწერებში",
+       "action-deletechangetags": "მონაცემთა ბაზიდან ტეგების წაშლა",
+       "action-purge": "ამ გვერდის წაშლა",
        "nchanges": "$1 ცვლილება",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ბოლო ვიზიტის შემდეგ}}",
        "enhancedrc-history": "ისტორია",
        "upload-http-error": "მოხდა HTTP შეცდომა: $1",
        "upload-copy-upload-invalid-domain": "ამ დომენში ატვირთვების კოპირება არ არის ხელმისაწვდომი.",
        "upload-foreign-cant-upload": "ეს ვიკი არ არის დაკონფიგურირებული იმისათვის, რომ ატვირთოს ფაილების უცხო ფაილთა საწყობში.",
+       "upload-dialog-disabled": "ფაილის ატვირთვა ამ დიალოგური ფანჯრით გათიშულია ამ ვიკიზე.",
        "upload-dialog-title": "ფაილის ატვირთვა",
        "upload-dialog-button-cancel": "გაუქმება",
        "upload-dialog-button-done": "შესრულდა",
        "uploadstash-badtoken": "მითითებული მოქმედება ვერ შესრულდა. შესაძლოა, რედაქტირების უფლებამოსილების მოქმედების ვადა ამოიწურა. გთხოვთ, სცადეთ თავიდან.",
        "uploadstash-errclear": "ფაილების გასუფთავება ვერ მოხერხდა.",
        "uploadstash-refresh": "ფაილების სიის განახლება",
+       "uploadstash-thumbnail": "მინიატურის ნახვა",
        "invalid-chunk-offset": "არასწორი საწყისი წერტილი",
        "img-auth-accessdenied": "მოქმედება აკრძალულია",
        "img-auth-nopathinfo": "დაკარგულია PATH_INFO.\nთქვენი სერვერი არ არის მომართული ამ ინფორმაციის გადასაცემად.\nშესაძლოა, ის მუშაობს CGI-ის ბაზაზე და არ გააჩნია img_auth მხარდაჭერა.\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization იხილეთ სურათის ავტორიზაცია.]",
        "filerevert-submit": "გაუქმება",
        "filerevert-success": "'''[[Media:$1|$1]]''' დაუბრუნდა ვერსიას [$4  $3, $2]-დან.",
        "filerevert-badversion": "არ არსებობს ფაილის წინა ლოკალური ვერსია მოთხოვნილი  თარიღითა და დროით",
+       "filerevert-identical": "ფაილის ამჟამინდელი ვერსია უკვე არის შერჩეულის იდენტური.",
        "filedelete": "$1 წაშლა",
        "filedelete-legend": "ფაილის წაშლა",
        "filedelete-intro": "თქვენ შლით <strong>[[Media:$1|$1]]-ს</strong> მისი ისტორიით.",
        "trackingcategories-msg": "კატეგორიის მიდევნება",
        "trackingcategories-name": "შეტყობინების სახელი",
        "trackingcategories-desc": "კატეგორიაში ჩართვის კრიტერიუმები",
+       "restricted-displaytitle-ignored": "გვერდები დაიგნორებული სათაურებით",
        "noindex-category-desc": "გვერდი არ არის ინდექსირებული საძიებო სამუშაოებით, რადგან მასზე არის „ჯადოსნური სიტყვა“ <code><nowiki>__NOINDEX__</nowiki></code> და ის იმყოფება სახელთა სივრცეში, სადაც დასაშვებია ეს დროშა.",
        "index-category-desc": "გვერდზე არის „ჯადოსნური სიტყვა“ <code><nowiki>__INDEX__</nowiki></code> (და გვერდი იმყოფება სახელთა სივრცეში, სადაც დაშვებულია ეს დროშა). ამიტომ იგი ინდექსირებულია საძიებო სამუშაოებით იმ შემთხვევებში, როცა ეს ჩვეულებრივ არ ხდება.",
        "post-expand-template-inclusion-category-desc": "გვერდის ზომა უფრო გაიზრდება <code>$wgMaxArticleSize</code> ყველა თარგის ჩვენების შემდეგ, ამიტომ ზოგიერთი მათგანი არ იყო ნაჩვენები მთლიანად.",
        "watchnologin": "რეგისტრაცია ვერ შესრულდა",
        "addwatch": "კონტროლის სიაში დამატება",
        "addedwatchtext": "„[[:$1]]“ და მისი განხილვის გვერდი დაემატა თქვენს [[Special:Watchlist|კონტროლის სიას]].",
+       "addedwatchtext-talk": "„[[:$1]]“ და მასთან დაკავშირებული გვერდი დაემატა თქვენს [[Special:Watchlist|კონტროლის სიას]].",
        "addedwatchtext-short": "გვერდი „$1“ დაემატა თქვენი კონტროლის სიას.",
        "removewatch": "კონტროლის სიიდან წაშლა",
        "removedwatchtext": "„[[:$1]]“ და მისი განხილვის გვერდი ამოღებულია თქვენი [[Special:Watchlist|კონტროლის სიიდან]].",
+       "removedwatchtext-talk": "„[[:$1]]“ და მასთან დაკავშირებული გვერდი ამოღებულია თქვენი [[Special:Watchlist|კონტროლის სიიდან]].",
        "removedwatchtext-short": "გვერდი „$1“ წაიშალა თქვენი კონტროლის სიიდან.",
        "watch": "კონტროლი",
        "watchthispage": "ამ გვერდის კონტროლი",
        "rollbacklinkcount": "$1 {{PLURAL:$1|ცვლილების|ცვლილების}} გაუქმება",
        "rollbacklinkcount-morethan": "$1-ზე მეტი {{PLURAL:$1|ცვლილების|ცვლილების}} გაუქმება",
        "rollbackfailed": "შეცდომა გაუქმებისას",
+       "rollback-missingparam": "აკლია საჭირო პარამეტრები.",
        "cantrollback": "შეუძლებელია უწინდელი რედაქციის აღდგენა; ის, ვინც უკანასკნელი ცვლილებები შეიტანა, ამ სტატიის ერთადერთი ავტორია.",
        "alreadyrolled": "შეუძლებელია ბოლო ცვლილების გაუქმება [[:$1]], გაკეებული [[User:$2|$2]] ([[User talk:$2|განხილვა]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nვიღაცა სხვამ უკვე შეასწორა ან გაააუქმა ეს გვერდი.\n\nბოლო ცვლილებები შეიტანა  [[User:$3|$3]] ([[User talk:$3|განხილვა]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "რედაქტირება განმარტებული იყო როგორც: <em>$1</em>.",
        "changecontentmodel-success-text": "[[:$1]]-ის კონტენტის ტიპი შეიცვალა.",
        "changecontentmodel-cannot-convert": "[[:$1]]-ის შინაარსის $2-ის ტიპზე კონვერტაცია შეუძლებელია.",
        "changecontentmodel-nodirectediting": "$1 შინაარსის მოდელს არ აქვს პირდაპირი რედაქტირების მხარდაჭერა",
+       "changecontentmodel-emptymodels-title": "შინაარსის მოდელები არ არის ხელმისაწვდომი",
+       "changecontentmodel-emptymodels-text": "შინაარსი [[:$1]]-ზე არ კონვერტირდება არცერთ ტიპზე.",
        "log-name-contentmodel": "შინაარსის მოდელის შეცვლის ჟურნალი",
        "log-description-contentmodel": "გვერდის შინაარსის მოდელთან დაკავშირებული მოვლენები",
        "logentry-contentmodel-new": "$1-მ {{GENDER:$2|შექმნა}} გვერდი $3, არა-სტანდარტული მოდელით \"$5\"",
        "undeletehistorynoadmin": "ეს სტატია წაშლილია. წაშლის მიზეზი ნაჩვენებია მოკლე ანოტაციაში ქვემოთ, იმ მომხმარებელთა დეტალებთან ერთად ვინც რედაქტირება გაუკეთა ამ გვერდს წაშლის წინ. იმ წაშლილი ტექსტების აქტუალური ვერსიები მიღწევადია მხოლოდ ადმინისტრატორებისათვის.",
        "undelete-revision": "$1-ის წაშლილი ვერსია ($5, $4-ის მდგომარეობით), შენახული მომხმარებლის $3 მიერ:",
        "undeleterevision-missing": "არასწორი ან არარსებული ვერსია. სავარაუდოდ ქვენ გადახვედით არასწორ ბმულზე, ან იგი წაიშალა არქივიდან.",
+       "undeleterevision-duplicate-revid": "{{PLURAL:$1|ერთი ცვლილება|$1 ცვლილება}} ვერ აღდგა, რადგან {{PLURAL:$1|მისი|მათი}} <code>rev_id</code> უკვე გამოიყენებოდა.",
        "undelete-nodiff": "წინა ცვლილება ვერ ვიპოვეთ.",
        "undeletebtn": "აღდგენა",
        "undeletelink": "ნახვა/აღდგენა",
        "invalidateemail": "ელ-ფოსტის დადასტურების გაუქმება",
        "notificationemail_subject_changed": "{{SITENAME}} რეგისტრირებული იმეილის მისამართები შეიცვალა",
        "notificationemail_subject_removed": "{{SITENAME}} რეგისტრირებული იმეილის მისამართები წაიშალა",
+       "notificationemail_body_changed": "ვიღაცამ, სავარაუდოდ თქვენ, IP მისამართიდან $1,\n{{SITENAME}}ში შეცვალა „$2“ ანგარიშის იმეილის მისამართი „$3“-ზე.\n\nთუ ეს თქვენ არ იყავით, გთხოვთ დაუკავშირდით საიტის ადმინისტრატორს სასწრაფოდ.",
+       "notificationemail_body_removed": "ვიღაცამ, სავარაუდოდ თქვენ, IP მისამართიდან $1,\n{{SITENAME}}ში წაშალა ანგარიშის „$2“ იმეილის მისამართი.\n\nთუ ეს თქვენ არ იყავით, გთხოვთ დაუკავშირდით საიტის ადმინისტრატორს სასწრაფოდ.",
        "scarytranscludedisabled": "[«Interwiki transcluding» გათიშულია]",
        "scarytranscludefailed": "[$1-თან დაკავშირების შეცდომა]",
        "scarytranscludefailed-httpstatus": "[ვერ მოხერხდა თარგის ჩატვირთვა $1-თვის: HTTP $2]",
        "confirm-unwatch-button": "დიახ",
        "confirm-unwatch-top": "მოვხსნა ეს გვერდი თქვენი კონტროლის სიიდან?",
        "confirm-rollback-button": "კარგი",
+       "confirm-rollback-top": "დავაბრუნოთ რედაქტირებები ამ გვერდზე?",
        "semicolon-separator": ";&#32;",
        "comma-separator": ",&#32;",
        "colon-separator": ":&#32;",
        "timezone-local": "ლოკალური",
        "duplicate-defaultsort": "'''ყურადღება.'''სორტირების გასაღებს «$2»-ს გააჭრის წინა გასაღებს «$1»-ს.",
        "duplicate-displaytitle": "<strong>ყურადღება:</strong> დისპლეის სათაური \"$2\" განსაზღვრავს ადრე გაცემულ დისპლეის სათაურს \"$1\".",
+       "restricted-displaytitle": "<strong>Warning:</strong> სათაური „$1“ იგნორირებულ იქნა, რადგან ის არ ემთხვევა გვერდის ნამდვილ სათაურს.",
        "invalid-indicator-name": "<strong>შეცდომა:</strong> გვერდის სტატუსის ინდიკატორი <code>name</code> ატრიბუტი არ უნდა იყოს ცარიელი.",
        "version": "ვერსია",
        "version-extensions": "დაყენებული გაფართოებები",
        "tags-delete-not-found": "აღნიშვნა „$1“ არ არსებობს.",
        "tags-delete-too-many-uses": "ტეგი \"$1\" მიღებულია $2 ვერსიებთან, რაც იმას ნიშნავს, რომ იგი არ შეიძლება იყოს წაშლილი",
        "tags-delete-warnings-after-delete": "ტეგი „$1“ წაიშალა, თუმცა აღმოჩენილია შემდეგი {{PLURAL:$2|შეტყობინება|შეტყობინებები}}:",
+       "tags-delete-no-permission": "თქვენ არ გაქვთ შეცვლილი ტეგების წაშლის უფლება.",
        "tags-activate-title": "ტეგის გააქტიურება",
        "tags-activate-question": "თქვენ ცდილობთ დასათაურების გააქტიურებას „$1“.",
        "tags-activate-reason": "მიზეზი:",
        "feedback-useragent": "მომხმარებლის აგენტი:",
        "searchsuggest-search": "ძიება",
        "searchsuggest-containing": "შეიცავს...",
+       "api-error-autoblocked": "თქვენი IP მისამართი ავტომატურად დაიბლოკა, რადგან ის გამოიყენა დაბლოკილმა მომხმარებელმა.",
        "api-error-badaccess-groups": "თქვენ არ გაქვთ ამ ვიკიში ფაილების ატვირთვის უფლება.",
        "api-error-badtoken": "შიდა შეცდომა: ცუდი ტოკენი.",
+       "api-error-blocked": "თქვენთვის რედაქტირება დაბლოკილია.",
        "api-error-copyuploaddisabled": "ამ სერვერზე URL-მისამართის საშუალებით ატვირთვა გამორთულია.",
        "api-error-duplicate": "საიტზე უკვე {{PLURAL:$1|არსებობს სხვა ფაილი|არსებობს სხვა ფაილები}} ანალოგიური შინაარსით.",
        "api-error-duplicate-archive": "საიტზე ადრე {{PLURAL:$1|უკვე იყო ფაილი}} ანალოგიური შინაარსით, მაგრამ {{PLURAL:$1|ის წაიშალა|ისინი წაიშალა}}.",
        "api-error-unknownerror": "უცნობი შეცდომა: „$1“.",
        "api-error-uploaddisabled": "ატვირთვის მექანიზმი ამ ვიკიზე გამორთულია",
        "api-error-verification-error": "ეს ფაილი ან რაიმე შეცდომას შეიცავს, ან არ აქვს სახელის გაფართოება.",
+       "api-error-was-deleted": "ფაილი ამ სახელწოდებით ადრე აიტვირთა და შემდეგ წაიშალა.",
        "duration-seconds": "$1 {{PLURAL:$1|წამი|წამი}}",
        "duration-minutes": "$1 {{PLURAL:$1|წუთი|წუთი}}",
        "duration-hours": "$1 {{PLURAL:$1|საათი|საათი}}",
        "sessionprovider-nocookies": "შესაძლოა ქუქები გათიშულია. გთხოვთ ჩართეთ და სცადეთ განმეორებით.",
        "randomrootpage": "შემთხვევითი ძირეული გვერდი",
        "log-action-filter-block": "ბლოკირების ტიპი:",
+       "log-action-filter-contentmodel": "შეინაარსის მოდელის მოდერაციის ტიპი:",
        "log-action-filter-delete": "წაშლის ტიპი:",
        "log-action-filter-import": "იმპორტის ტიპი:",
        "log-action-filter-managetags": "ტეგის ცვლილების ტიპი:",
        "log-action-filter-patrol": "შემოწმების ტიპი:",
        "log-action-filter-protect": "დაცვის ტიპი:",
        "log-action-filter-rights": "უფლების შეცვლის ტიპი:",
+       "log-action-filter-suppress": "დამალვის ტიპი:",
        "log-action-filter-upload": "ატვირთვის ტიპი:",
        "log-action-filter-all": "ყველა",
        "log-action-filter-block-block": "დაბლოკვა",
        "log-action-filter-block-reblock": "ბლოკირების შეცვლა",
        "log-action-filter-block-unblock": "განბლოკვა",
+       "log-action-filter-contentmodel-change": "შინაარსის მოდელის შეცვლა",
+       "log-action-filter-contentmodel-new": "გვერდის შექმნა არასტანდარტული შინაარსის მოდელით",
+       "log-action-filter-delete-delete": "გვერდის წაშლა",
+       "log-action-filter-delete-restore": "გვერდის აღდგენა",
+       "log-action-filter-delete-event": "ჯურნალის ჩანაწერის წაშლა",
+       "log-action-filter-delete-revision": "ვერსიის წაშლა",
+       "log-action-filter-import-interwiki": "Transwiki-ს იმპორტი",
+       "log-action-filter-import-upload": "XML ატვირთვიდან იმპორტი",
+       "log-action-filter-managetags-create": "ტეგის შექმნა",
+       "log-action-filter-managetags-delete": "ტეგის წაშლა",
+       "log-action-filter-managetags-activate": "ტეგის აქტივაცია",
+       "log-action-filter-managetags-deactivate": "ტეგის დეაქტივაცია",
+       "log-action-filter-move-move": "გადატანა გადამისამართებების გადაწერის გარეშე",
+       "log-action-filter-move-move_redir": "გადატანა გადამისამართებების გადაწერით",
+       "log-action-filter-newusers-create": "შექმნა ანონიმური მომხმარებლის მიერ",
        "log-action-filter-newusers-create2": "დარეგისტრირებული მომხმარებლის შექმნა",
        "log-action-filter-newusers-autocreate": "ავტომატური შექმნა",
        "log-action-filter-newusers-byemail": "პაროლით შექმნა, რომელიც გამოიგზავნა იმეილით",
+       "log-action-filter-patrol-patrol": "ხელით შემოწმება",
        "log-action-filter-patrol-autopatrol": "ავტომატური შემოწმება",
        "log-action-filter-protect-protect": "დაცვა",
+       "log-action-filter-protect-modify": "დაცვის შეცვლა",
        "log-action-filter-protect-unprotect": "დაცვის მოხსნა",
        "log-action-filter-protect-move_prot": "დაცვა გადატანისაგან",
+       "log-action-filter-rights-rights": "ხელით შეცვლა",
        "log-action-filter-rights-autopromote": "ავტომატური შეცვლა",
+       "log-action-filter-suppress-event": "ჟურნალის დამალვა",
+       "log-action-filter-suppress-revision": "ცვლილების დამალვა",
+       "log-action-filter-suppress-delete": "გვერდის დამალვა",
+       "log-action-filter-suppress-block": "მომხმარებლის დამალვა ბლოკით",
+       "log-action-filter-suppress-reblock": "მომხმარებლის დამალვა ხელახლა ბლოკირებით",
        "log-action-filter-upload-upload": "ახალი ატვირთვა",
        "log-action-filter-upload-overwrite": "ხელახლა ატვირთვა",
+       "authmanager-authn-not-in-progress": "აუტენტიფიკაცია არ მიმდინარეობს ან სესიის მონაცემი დაიკარგა. გთხოვთ დაიწყეთ თავიდან.",
+       "authmanager-authn-no-primary": "მოწოდებული მონაცემები ვერ იქნა აუტენტიფიცირებული.",
+       "authmanager-authn-no-local-user": "მოწოდებული მონაცემები არ უკავშირდება არცერთ მომხმარებელს ამ ვიკიზე.",
+       "authmanager-create-disabled": "ანგარიშის შექმნა გათიშულია.",
+       "authmanager-create-from-login": "ანგარიშის შესაქმნელად, გთხოვთ შეავსეთ ქვემოთ მოცემული ველები.",
+       "authmanager-create-not-in-progress": "ანგარიშის შექმა არ მიმდინარეობს ან სესიის მონაცემი დაიკარგა. გთხოვთ დაიწყეთ თავიდან.",
+       "authmanager-create-no-primary": "მოწოდებული მონაცემები არ გამოდგა ანგარიშის შესაქმნელად.",
+       "authmanager-link-no-primary": "მოწოდებული მონაცემები არ გამოდგა ანგარიშის დასაკავშირებლად.",
+       "authmanager-link-not-in-progress": "ანგარიშის დაკავშირება არ მიმდინარეობს ან სესიის მონაცემი დაიკარგა. გთხოვთ დაიწყეთ თავიდან.",
        "authmanager-authplugin-setpass-failed-title": "პაროლის ცვლილება ვერ განხორციელდა",
+       "authmanager-authplugin-setpass-failed-message": "აუთენთიფიკაციის პლაგინმა უარყო პაროლის ცვლილება.",
+       "authmanager-authplugin-create-fail": "აუთენთიფიკაციის პლაგინმა უარყო ანგარიშის შექმნა.",
+       "authmanager-authplugin-setpass-denied": "აუთენთიფიკაციის პლაგინი არ იძლევა პაროლების გამოცვლის უფლებას.",
+       "authmanager-authplugin-setpass-bad-domain": "არასწორი დომეინი.",
+       "authmanager-autocreate-noperm": "ავტომატური ანგარიშის შექმნა არ არის ნებადართული.",
+       "authmanager-autocreate-exception": "ავტომატური ანგარიშის შექმნა დროებით გათიშულია ადრინდელი ხარვეზების გამო.",
+       "authmanager-userdoesnotexist": "მომხმარებლის ანგარიში „$1“ არ არის რეგისტრირებული",
+       "authmanager-username-help": "მომხმარებლის სახელი აუთენთიფიკაციისთვის.",
+       "authmanager-password-help": "პაროლი აუთენთიფიკაციისთვის.",
+       "authmanager-domain-help": "დომეინი გარე აუთენთიფიკაციისთვის.",
+       "authmanager-retype-help": "დასადასტურებლად კვლავ შეიყვანეთ პაროლი.",
        "authmanager-email-label": "ელ. ფოსტა",
        "authmanager-email-help": "ელ. ფოსტის მისამართი",
        "authmanager-realname-label": "ნამდვილი სახელი",
        "authmanager-realname-help": "მომხმარებლის ნამდვილი სახელი",
+       "authmanager-provider-password": "პაროლზე დაფუძნებული აუთენთიფიკაცია",
+       "authmanager-provider-password-domain": "პაროლზე და დომეინზე დაფუძნებული აუთენთიფიკაცია",
+       "authmanager-provider-temporarypassword": "დროებითი პაროლი",
+       "authprovider-confirmlink-request-label": "ანგარიშები, რომლებიც დაკავშირებული უნდა იყოს",
+       "authprovider-confirmlink-success-line": "$1: დაუკავშირდა წარმატებით.",
+       "authprovider-confirmlink-failed": "ანგარიშის დაკავშირება არ შესრულდა სრულად: $1",
+       "authprovider-confirmlink-ok-help": "გააგრძელე დაკავშირების ჩავარდნის შეტყობინებების ჩვენების შემდეგ.",
        "authprovider-resetpass-skip-label": "გამოტოვება",
        "authprovider-resetpass-skip-help": "გამოტოვეთ პაროლის შეცვლის პროცესი.",
+       "authform-nosession-login": "ავტორიზაციამ წარმატებით ჩაიარა, თუმცა თქვენი ბრაუზერი ვერ ახერხებს მის „დამახსოვრებას“.\n\n$1",
+       "authform-nosession-signup": "ანგარიში შეიქმნა, თუმცა თქვენი ბრაუზერი ვერ ახერხებს მის „დამახსოვრებას“.\n\n$1",
+       "authform-newtoken": "არ არის ტოკენი. $1",
+       "authform-notoken": "არ არის ტოკენი",
+       "authform-wrongtoken": "არასწორი ტოკენი",
        "specialpage-securitylevel-not-allowed-title": "არ არის ნებადართული",
-       "credentialsform-account": "ანგარიშის სახელი:"
+       "specialpage-securitylevel-not-allowed": "უკაცრავად, თქვე არ შეგიძლიათ ამ გვერდის გამოყენება, რადგან თქვენი იდენთიფიკაცია არ არის დადასტურებული.",
+       "authpage-cannot-login": "შეუძლებელია ავტორიზაციის დაწყება.",
+       "authpage-cannot-login-continue": "შეუძლებელია ავტორიზაციის გაგრძელება. სავარაუდოდ, თქვენი სესიის დრო ამოიწურა.",
+       "authpage-cannot-create": "შეუძლებელია ანგარიშის შექმნის დაწყება.",
+       "authpage-cannot-create-continue": "შეუძლებელია ანგარიშის შექმნის გაგრძელება. სავარაუდოდ, თქვენი სესიის დრო ამოიწურა.",
+       "authpage-cannot-link": "შეუძლებელია ანგარიშის დაკავშირების დაწყება.",
+       "authpage-cannot-link-continue": "შეუძლებელია ანგარიშის დაკავშირების გაგრძელება. სავარაუდოდ, თქვენი სესიის დრო ამოიწურა.",
+       "cannotauth-not-allowed-title": "ნებართვა უარყოფილია",
+       "cannotauth-not-allowed": "თქვენ არ გაქვთ ამ გვერდის გამოყენების უფლება",
+       "changecredentials": "მონაცემების შეცვლა",
+       "changecredentials-submit": "მონაცემების შეცვლა",
+       "changecredentials-invalidsubpage": "$1 არ არის ვალიდური მონაცემის ტიპი.",
+       "changecredentials-success": "თქვენი მონაცემები შეიცვალა.",
+       "removecredentials": "მონაცემების წაშლა",
+       "removecredentials-submit": "მონაცემების წაშლა",
+       "removecredentials-invalidsubpage": "$1 არ არის ვალიდური მონაცემის ტიპი.",
+       "removecredentials-success": "თქვენი მონაცემები წაიშალა.",
+       "credentialsform-provider": "მონაცემის ტიპი:",
+       "credentialsform-account": "ანგარიშის სახელი:",
+       "cannotlink-no-provider-title": "არ არის დაკავშირებული ანგარიშები",
+       "cannotlink-no-provider": "არ არის დაკავშირებული ანგარიშები.",
+       "linkaccounts": "ანგარიშების დაკავშირება",
+       "linkaccounts-success-text": "ანგარიში დაკავშირებულია.",
+       "linkaccounts-submit": "ანგარიშების დაკავშირება",
+       "unlinkaccounts": "ანგარიშებისთვის დაკავშირების მოშორება",
+       "unlinkaccounts-success": "ანგარიშს მოეხსნა დაკავშირება.",
+       "userjsispublic": "შენიშვნა: ჯავასკრიპტის ქვეგვერდები არ უნდა შეიცავდეს პირადულ მონაცემებს, რადგან მას სხვა მომხმარებლებიც ხედავენ.",
+       "usercssispublic": "შენიშვნა: CSS ქვეგვერდები არ უნდა შეიცავდეს პირადულ მონაცემებს, რადგან მას სხვა მომხმარებლებიც ხედავენ."
 }
index b413646..5eda368 100644 (file)
        "yourname": "Namê karberi:",
        "yourpassword": "Parola:",
        "yourpasswordagain": "Parola tekrar ke:",
-       "remembermypassword": "Cıkotena mı na komputeri de bia ho viri (seba tewr jêde $1 {{PLURAL:$1|roze|rozu}})",
        "yourdomainname": "Bandıra sıma:",
        "externaldberror": "Cıfeteliyaisê naskerdene de ya xeta esta ya ki tebera vırastena hesabê sıma rê destur çino.",
        "login": "Cı kuye",
index 8e077ea..08b84e8 100644 (file)
        "yourpasswordagain": "Құпия сөзді қайталаңыз:",
        "createacct-yourpasswordagain": "Құпия сөзді құптаңыз",
        "createacct-yourpasswordagain-ph": "Құпия сөзіңізді қайтадан енгізіңіз",
-       "remembermypassword": "Тіркелгімді осы браузерде ұмытпа (ең көбі $1 {{PLURAL:$1|күн|күн}})",
        "userlogin-remembermypassword": "Мені жүйеде сақтап қою",
        "userlogin-signwithsecure": "Қауіпсіз байланысуды қолдану",
        "cannotloginnow-title": "Қазір шығу мүмкін емес",
        "createacct-reason-ph": "Неге басқа тіркегі жасамақшысыз",
        "createacct-submit": "Тіркелгіңізді жасаңыз",
        "createacct-another-submit": "Тіркелгі жасау",
+       "createacct-continue-submit": "Тіркелуді жалғастыру",
        "createacct-benefit-heading": "{{SITENAME}} сіздермен жасалады.",
        "createacct-benefit-body1": "{{PLURAL:$1|өңдеме|өңдеме}}",
        "createacct-benefit-body2": "{{PLURAL:$1|бет|бет}}",
index 92b86c0..74d60e9 100644 (file)
        "undelete_short": "{{PLURAL:$1|편집 한 개|편집 $1개}} 되살리기",
        "viewdeleted_short": "{{PLURAL:$1|삭제된 편집 한 개|삭제된 편집 $1개}} 보기",
        "protect": "보호",
-       "protect_change": "ë³´í\98¸ ì\88\98ì¤\80 ë°\94꾸기",
+       "protect_change": "바꾸기",
        "protectthispage": "이 문서 보호하기",
        "unprotect": "보호 설정 바꾸기",
        "unprotectthispage": "이 문서의 보호 설정을 바꾸기",
        "yourpasswordagain": "비밀번호 다시 입력:",
        "createacct-yourpasswordagain": "비밀번호 확인",
        "createacct-yourpasswordagain-ph": "비밀번호를 다시 입력하세요",
-       "remembermypassword": "이 브라우저에 로그인 상태 저장하기 (최대 $1일)",
        "userlogin-remembermypassword": "로그인 상태를 유지하기",
        "userlogin-signwithsecure": "보안 연결 사용",
+       "cannotlogin-title": "로그인할 수 없음",
+       "cannotlogin-text": "로그인할 수 없습니다.",
        "cannotloginnow-title": "지금 로그인할 수 없습니다.",
        "cannotloginnow-text": "$1 사용 중에는 로그인이 불가능합니다.",
+       "cannotcreateaccount-title": "계정을 만들 수 없습니다",
+       "cannotcreateaccount-text": "이 위키에서 직접 계정 만들기는 활성화되어 있지 않습니다.",
        "yourdomainname": "도메인 이름:",
        "password-change-forbidden": "이 위키에서 비밀번호를 바꿀 수 없습니다.",
        "externaldberror": "인증 데이터베이스에 오류가 있거나 바깥 계정을 새로 고칠 권한이 없습니다.",
        "filerevert-submit": "되돌리기",
        "filerevert-success": "'''[[Media:$1|$1]]''' 파일을 [$4 $2 $3 버전]으로 되돌렸습니다.",
        "filerevert-badversion": "입력된 시간 기록을 가진 파일의 로컬 버전이 없습니다.",
+       "filerevert-identical": "파일의 현재 버전은 선택한 것과 이미 동일합니다.",
        "filedelete": "$1 삭제하기",
        "filedelete-legend": "파일 삭제하기",
        "filedelete-intro": "'''[[Media:$1|$1]]''' 파일과 모든 역사를 삭제합니다.",
        "pageinfo-article-id": "문서 ID",
        "pageinfo-language": "문서 내용 언어",
        "pageinfo-content-model": "문서 내용 모델",
+       "pageinfo-content-model-change": "변경",
        "pageinfo-robot-policy": "로봇에 의한 색인",
        "pageinfo-robot-index": "허용됨",
        "pageinfo-robot-noindex": "불허됨",
index 1994b0d..5c34a58 100644 (file)
        "yourpasswordagain": "Tesseram adfirmare:",
        "createacct-yourpasswordagain": "Tesseram confirmare",
        "createacct-yourpasswordagain-ph": "Tesseram iterum inscribe",
-       "remembermypassword": "Tesseram meam hoc in navigatro inter conventa memento ({{PLURAL:$1|die|diebus}} $1 tenus)",
        "userlogin-remembermypassword": "Nomen meum retineatur",
        "yourdomainname": "Regnum tuum:",
        "login": "Nomen dare",
        "minoredit": "Haec est recensio minor",
        "watchthis": "Hanc paginam observare",
        "savearticle": "Hanc redactionem servare",
+       "savechanges": "Mutationes divulgare",
        "publishpage": "Hanc paginam divulgare",
        "publishchanges": "Hanc recensionem divulgare",
        "preview": "Praevidere",
index 510249a..4c42043 100644 (file)
        "yourpasswordagain": "Passwuert nach eemol antippen:",
        "createacct-yourpasswordagain": "Passwuert confirméieren",
        "createacct-yourpasswordagain-ph": "Passwuert nach eng Kéier aginn",
-       "remembermypassword": "Meng Umeldung op dësem Computer (fir maximal $1 {{PLURAL:$1|Dag|Deeg}}) verhalen",
        "userlogin-remembermypassword": "Mech ageloggt halen",
        "userlogin-signwithsecure": "Eng sécher Verbindung benotzen",
        "cannotloginnow-title": "Aloggen ass elo net méiglech",
        "filerevert-submit": "Zrécksetzen",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong> gouf op d'[$4 Versioun vum $2, $3 Auer] zréckgesat.",
        "filerevert-badversion": "Et gëtt keng vireg lokal Versioun vun deem Fichier mat der Zäitinformatioun déi Dir uginn hutt.",
+       "filerevert-identical": "Déi aktuell Versioun vum Fichier ass identesch mat där erausgesichter.",
        "filedelete": "Läsch \"$1\"",
        "filedelete-legend": "Fichier läschen",
        "filedelete-intro": "Dir läscht de Fichier '''[[Media:$1|$1]]''' mat all senge Versiounen (Historique).",
        "specialpages-group-changes": "Rezent Ännerungen a Lëschten",
        "specialpages-group-media": "Medie-Rapporten an eropgeluede Fichieren",
        "specialpages-group-users": "Benotzer a Rechter",
-       "specialpages-group-highuse": "Dacks benotzte Säiten",
+       "specialpages-group-highuse": "Dacks benotzt Säiten",
        "specialpages-group-pages": "Lëschte vu Säiten",
        "specialpages-group-pagetools": "Handwierksgeschir fir Säiten",
        "specialpages-group-wiki": "Daten an Handwierksgeschir",
index 8de4353..79cea0e 100644 (file)
@@ -61,7 +61,7 @@
        "underline-always": "Sempre",
        "underline-never": "Mâi",
        "underline-default": "Impostassioin predefinie do navegatô o da skin",
-       "editfont-style": "Stile do carattere de l'aera de modiffica",
+       "editfont-style": "Stile do carattere de l'area de modiffica",
        "editfont-default": "Predefinio do navegatô",
        "editfont-monospace": "Carattere a larghessa fissa",
        "editfont-sansserif": "Carattere sans-serif",
        "yourpasswordagain": "Riscrivi a pòula segrétta:",
        "createacct-yourpasswordagain": "Conferma a password",
        "createacct-yourpasswordagain-ph": "Conferma a password un'atra votta",
-       "remembermypassword": "Aregòrda a mæ login in sto navegatô (pe in mascimo de $1 {{PLURAL:$1|giórno|giórni}})",
        "userlogin-remembermypassword": "Mantegnime collegou",
        "userlogin-signwithsecure": "Adoeuvia una conescion segua",
        "cannotloginnow-title": "Aoa no se poeu intrâ",
        "subject-preview": "Anteprimma do soggetto:",
        "previewerrortext": "Gh'è stæto un errô mentre se çercava de mostrâ l'anteprimma.",
        "blockedtitle": "L'utente o l'é bloccòu",
-       "blockedtext": "''''O to nomme utente ò adresso IP o l'è stæto bloccòu.'''\n\nO blòcco o l'è stæto fæto da $1. A raxon dæta a l'è ''$2''.\n\n* Iniçio do blocco: $8\n* Fin do blocco: $6\n* Utente blocou: $7\n\nL'è poscibbile contattâ $1 o un âtro [[{{MediaWiki:Grouppage-sysop}}|amministratô]] pe discûtte inscio blòcco.\nNo ti poeu doeuviâ o comando \"Manda un'e-mail a st'ûtente\" se no ti g'hæ 'n adresso e-mail registròu inte to [[Special:Preferences|preferençe]] e se no t'ê stæto bloccòu ascì.\nO to adresso IP o l'è $3, e o to blòcco ID o l'è #$5.\nPe piaxei, pe domandâ informaçioin, speçifficali tutti doî.",
+       "blockedtext": "''''O to nomme utente ò adresso IP o l'è stæto bloccòu.'''\n\nO blòcco o l'è stæto fæto da $1. A raxon dæta a l'è ''$2''.\n\n* Prinçippio do blocco: $8\n* Fin do blocco: $6\n* Utente blocou: $7\n\nL'è poscibbile contattâ $1 ò un atro [[{{MediaWiki:Grouppage-sysop}}|amministratô]] pe discûtte inscio blòcco.\nNo ti poeu doeuviâ o comando \"Manda un'e-mail a st'utente\" se no ti g'hæ 'n adreçço e-mail registròu inte to [[Special:Preferences|preferençe]] e se no t'ê stæto bloccòu ascì.\nO to adreçço IP o l'è $3, e o to blòcco ID o l'è #$5.\nPe piaxei, pe domandâ de informaçioin, speçifficali tutti doî.",
        "autoblockedtext": "O teu addresso IP o l'è stæto bloccòu outomaticamente perché o l'ea za usòu da 'n âtro utente, bloccòu da $1.\nA raxon dæta a l'è stæta:\n\n:''$2''\n\n* Prinsippio do blòcco: $8\n* Fin do blòcco: $6\n\nTi peu contattâ $1 ò un âtro\n[[{{MediaWiki:Grouppage-sysop}}|amministratô]] pe discutte o blòcco.\n\nDanni a mente a che no ti pêu ûsâ o comando \"manda 'na e-mail a sto utente\" se non ti g'hæ 'n addresso de posta elettronega registròu in te têu [[Special:Preferences|preferense]] e se ti no t'ê stæto bloccòu ascì.\n\nO to adresso IP o l'è $3, e o to blòcco ID o l'è #$5. Pe piaxei, pe domandâ informaçioin, speçifficali tutti doî.",
        "blockednoreason": "nisciun-a motivaçion dæta",
        "whitelistedittext": "Pe modificâ e paggine l'è necessaio $1.",
        "defemailsubject": "Messaggio da {{SITENAME}} da l'utente \"$1\"",
        "usermaildisabled": "e-mail utente disabilitâ",
        "usermaildisabledtext": "No l'è poscibbile inviâ de e-mail a di atri utenti insce questo wiki",
-       "noemailtitle": "Nisciun adreççoo e-mail",
+       "noemailtitle": "Nisciun adreçço e-mail",
        "noemailtext": "Questo utente o no l'ha indicou un adreçço e-mail vallido.",
        "nowikiemailtext": "Questo utente o l'ha scerto de no riçeive messaggi de posta elettronica da-i atri utenti.",
        "emailnotarget": "Nomme utente do destinataio inexistente o non vallido.",
        "emailto": "A:",
        "emailsubject": "Sogetto:",
        "emailmessage": "Messaggio:",
-       "emailsend": "Spèdi",
-       "emailccme": "Mandame unn-a copia do messagio co unn-a lettìa elettronega.",
+       "emailsend": "Spedisci",
+       "emailccme": "Mandime una copia do messaggio a-o mæ adreçço.",
        "emailccsubject": "Coppia do messaggio inviou a $1: $2",
        "emailsent": "E-mail spedïa",
        "emailsenttext": "A teu e-mail a l'è stæta spedïa.",
        "undeletedrevisions": "{{PLURAL:$1|Una verscion recuperâ|$1 verscioin recuperæ}}",
        "undeletedrevisions-files": "{{PLURAL:$1|Una verscion|$1 verscioin}} e $2 file recuperæ",
        "undeletedfiles": "{{PLURAL:$1|Un file recuperou|$1 file recuperæ}}",
-       "cannotundelete": "Ripristino non riuscio:\n$1",
+       "cannotundelete": "Çerti ò tutti i ripristini non riuscii:\n$1",
        "undeletedpage": "'''A pagina $1 a l'è stæta recuperâ'''\n\nConsurta o [[Special:Log/delete|registro de scançellaçioin]] pe vedde e scançellaçioin e i recupperi ciù reçente.",
        "undelete-header": "Consurta o [[Special:Log/delete|registro de scançellaçioin]] pe vedde e scassatue ciù reçente.",
        "undelete-search-title": "Çerca inte pagine scassæ",
        "sp-contributions-newbies-sub": "Pe i nêuvi ûtenti",
        "sp-contributions-newbies-title": "Contribuçioin di noeuvi utenti",
        "sp-contributions-blocklog": "Blòcchi",
-       "sp-contributions-suppresslog": "contributi utente soppresci",
-       "sp-contributions-deleted": "contributi utente scassæ",
+       "sp-contributions-suppresslog": "contributi {{GENDER:$1|utente}} soppresci",
+       "sp-contributions-deleted": "contributi {{GENDER:$1|utente}}  scassæ",
        "sp-contributions-uploads": "caregaménti",
        "sp-contributions-logs": "log",
        "sp-contributions-talk": "Ciæti",
        "export-templates": "Inciodi i template",
        "export-pagelinks": "Includdi paggine colegæ scin a 'na profonditæ de:",
        "export-manual": "Azonzi paggine manoalmente:",
-       "allmessages": "Messaggi do scistemma",
+       "allmessages": "Messaggi do scistema",
        "allmessagesname": "Nomme",
        "allmessagesdefault": "Testo predefinio",
        "allmessagescurrent": "Testo corrente",
index 86a2ba4..8633f4f 100644 (file)
        "yourpasswordagain": "Pakartokite slaptažodį:",
        "createacct-yourpasswordagain": "Patvirtinkite slaptažodį",
        "createacct-yourpasswordagain-ph": "Įveskite slaptažodį dar kartą",
-       "remembermypassword": "Prisiminti prisijungimo duomenis šiame kompiuteryje (daugiausiai $1 {{PLURAL:$1|dieną|dienas|dienų}})",
        "userlogin-remembermypassword": "Įsiminti mane",
        "userlogin-signwithsecure": "Naudoti saugią jungtį",
        "cannotloginnow-title": "Dabar negalima prisijungti",
index 25e8040..e528dde 100644 (file)
        "yourpasswordagain": "Atkārto paroli",
        "createacct-yourpasswordagain": "Apstipriniet paroli",
        "createacct-yourpasswordagain-ph": "Vēlreiz ievadiet paroli",
-       "remembermypassword": "Atcerēties pēc pārlūka aizvēršanas (spēkā ne vairāk kā $1 {{PLURAL:$1|dienas|diena|dienas}}).",
        "userlogin-remembermypassword": "Atcerēties mani",
        "userlogin-signwithsecure": "Izmantot drošu savienojumu",
        "yourdomainname": "Tavs domēns",
        "prefs-resetpass": "Mainīt paroli",
        "prefs-changeemail": "Mainīt vai noņemt e-pastu",
        "prefs-setemail": "Uzstādīt e-pasta adresi",
-       "prefs-email": "E-pasta uzstādījumi",
+       "prefs-email": "E-pasta iestatījumi",
        "prefs-rendering": "Izskats",
        "saveprefs": "Saglabāt",
        "restoreprefs": "Atjaunot noklusētos uzstādījumus (visās sadaļās)",
index 2e0455e..87cc76a 100644 (file)
        "yourpasswordagain": "Повторете ја лозинката:",
        "createacct-yourpasswordagain": "Потврда на лозинката",
        "createacct-yourpasswordagain-ph": "Повторно внесете ја лозинката",
-       "remembermypassword": "Запомни ме на овој сметач (највеќе $1 {{PLURAL:$1|ден|дена}})",
        "userlogin-remembermypassword": "Запомни ме",
        "userlogin-signwithsecure": "Користи безбеден опслужувач",
+       "cannotlogin-title": "Не можам да ве најавам",
+       "cannotlogin-text": "Најавата не е возможна.",
        "cannotloginnow-title": "Во моментов не можам да ве најавм",
        "cannotloginnow-text": "Не можам да ве најавам кога се користи $1.",
+       "cannotcreateaccount-title": "Не можам да создавам сметки",
+       "cannotcreateaccount-text": "Непосредното создавање на сметки не е овозможено на ова вики.",
        "yourdomainname": "Вашиот домен:",
        "password-change-forbidden": "Не можете да ја менувате лозинката на ова вики.",
        "externaldberror": "Настана грешка при надворешното најавување на базата или пак немате дозвола да ја подновите вашата надворешна сметка.",
        "file-thumbnail-no": "Името на податотеката почнува со <strong>$1</strong>.\nИзгледа дека е слика со намалена големина ''(мини, thumbnail)''.\nАко ја имате оваа слика во изворна големина, подигнете ја неја. Во спротивно сменете го името на податотеката.",
        "fileexists-forbidden": "Податотека со тоа име веќе постои и не може да биде заменета.\nАко и понатаму сакате да ја подигнете вашата податотеката, ве молиме вратете се назад и подигнете ја под друго име. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Во заедничкото складиште веќе постои податотека со ова име.\nАко и понатаму сакате да ја подигнете, вратете се и подигнете ја под друго име. \n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Подигањето е истоветен дупликат на тековната верзија на <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Подигањето е истоветен дупликат на {{PLURAL:$2|постара верзија|постари верзии}} на <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Оваа податотека е дупликат со {{PLURAL:$1|следнава податотека|следниве податотеки}}:",
        "file-deleted-duplicate": "Податотека индентична со податотеката ([[:$1]]) претходно била избришана. Треба да проверите во дневникот на бришења за оваа податотека пред повторно да ја подигнете.",
        "file-deleted-duplicate-notitle": "Податотека сосем иста како оваа била претходно избришана, а насловот бил притаен.\nТреба да побарате од некој што има можност да гледа податоци за притаени податотеки да ја разгледа ситуацијата пред да продолжите со преподигањето.",
        "backend-fail-connect": "Не можев да се поврзам со складишната основа „$1“.",
        "backend-fail-internal": "Се појави непозната грешка во складишната основа „$1“.",
        "backend-fail-contenttype": "Не можев да утврдам каква содржина има податотеката што треба да ја складирам во „$1“.",
-       "backend-fail-batchsize": "Складишната основа доби блок од $1 податочна {{PLURAL:$1|операција|операции}}, а ограничувањето е $2 {{PLURAL:$2|операција|операции}}.",
+       "backend-fail-batchsize": "Складишната основа доби блок од $1 {{PLURAL:$1|податотечна постапка|податотечни постапки}}, а ограничувањето е $2 {{PLURAL:$2|постапка|постапки}}.",
        "backend-fail-usable": "Не можев да ја прочитам или запишам податотеката „$1“ бидејќи немате доволно дозволи или поради тоа што недостасуваат именици/содржатели.",
        "filejournal-fail-dbconnect": "Не можев да се поврзам со дневничката база за складишната основа „$1“.",
        "filejournal-fail-dbquery": "Не можев да ја подновам дневничката база за складишната основа „$1“.",
        "filerevert-submit": "Врати",
        "filerevert-success": "'''[[Media:$1|$1]]''' е вратен на [$4 верзијата од $3, $2].",
        "filerevert-badversion": "Нема претходна месна верзија на оваа податотека со даденото време.",
+       "filerevert-identical": "Тековната верзија на податотеката е веќе истоветна на избраната.",
        "filedelete": "Избриши го $1",
        "filedelete-legend": "Избриши податотека",
        "filedelete-intro": "Ја бришете податотеката '''[[Media:$1|$1]]''' заедно со нејзината историја.",
        "pageinfo-article-id": "Назнака на страницата",
        "pageinfo-language": "Јазик на содржината на страницата",
        "pageinfo-content-model": "Модел на содржината на страницата",
+       "pageinfo-content-model-change": "смени",
        "pageinfo-robot-policy": "Индексирање со роботи",
        "pageinfo-robot-index": "Дозволено",
        "pageinfo-robot-noindex": "Недозволено",
        "unit-pixel": "п",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "Да го исчистам меѓускладот на страницава?",
-       "confirm-purge-bottom": "Со Ð¾Ð²Ð°Ð° Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\98а се чисти опслужувачкиот меѓусклад и се прикажува најновата верзија.",
+       "confirm-purge-bottom": "Со Ð¾Ð²Ð°Ð° Ð¿Ð¾Ñ\81Ñ\82апка се чисти опслужувачкиот меѓусклад и се прикажува најновата верзија.",
        "confirm-watch-button": "ОК",
        "confirm-watch-top": "Да ја додадам страницава во набљудуваните?",
        "confirm-unwatch-button": "ОК",
index ba463cd..533ebef 100644 (file)
        "yourpasswordagain": "तुमचा परवलीचा शब्द पुन्हा टंका:",
        "createacct-yourpasswordagain": "परवलीच्या शब्दाची निश्चिती करा",
        "createacct-yourpasswordagain-ph": "पुन्हा परवलीचा शब्द टाका",
-       "remembermypassword": "माझा सनोंदप्रवेश (लॉग-ईन) या न्याहाळकावर लक्षात ठेवा (जास्तीत जास्त $1 {{PLURAL:$1|दिवसासाठी|दिवसांसाठी}})",
        "userlogin-remembermypassword": "मला नोंदीकृतच(लॉग्ड-ईन) ठेवा",
        "userlogin-signwithsecure": "सुरक्षित अनुबंध(सेक्युअर कनेक्शन) वापरा",
        "cannotloginnow-title": "आता सनोंद प्रवेश घेऊ शकत नाही",
        "filerevert-submit": "पूर्वपदास न्या",
        "filerevert-success": "[$3, $2 प्रमाणे आवर्तन $4]कडे<strong>[[Media:$1|$1]]</strong>उलटवण्यात आली.",
        "filerevert-badversion": "दिलेलेल्या वेळ मापनानुसार,या संचिकेकरिता कोणतीही पूर्वीची स्थानिक आवृत्ती नाही.",
+       "filerevert-identical": "या संचिकेची सध्याची आवृत्ती ही निवड केलेल्या आवृत्तीसमच आहे.",
        "filedelete": "$1 वगळा",
        "filedelete-legend": "संचिका वगळा",
        "filedelete-intro": "तुम्ही<strong>[[Media:$1|$1]]</strong>त्याच्या सर्व इतिहासासह,वगळण्याच्या तयारीत आहात.",
index 482171a..fb7b918 100644 (file)
        "yourpasswordagain": "စကားဝှက် ပြန်​ရိုက်​ပါ -",
        "createacct-yourpasswordagain": "စကားဝှက်ကို အတည်ပြုပါ",
        "createacct-yourpasswordagain-ph": "စကားဝှက်ကို ထပ်မံ ရိုက်ထည့်ပါ",
-       "remembermypassword": "ဤ​ကွန်​ပျူ​တာ​တွင်​ ကျွန်ုပ်ကို ​မှတ်​ထား​ရန် (အများဆုံး $1 {{PLURAL:$1|ရက်|ရက်}}ကြာ)",
        "userlogin-remembermypassword": "Log in ဝင်ထားမည်",
        "userlogin-signwithsecure": "လုံခြုံသော ဆက်သွယ်မှုကို သုံးမည်",
        "yourdomainname": "သင့်ဒိုမိန်း -",
        "minoredit": "အရေးမကြီးသော ​ပြင်​ဆင်​မှု ​ဖြစ်​သည်​",
        "watchthis": "ဤစာမျက်နှာကို စောင့်ကြည့်ရန်",
        "savearticle": "ဤစာမျက်နှာကို သိမ်းရန်",
+       "savechanges": "ပြောင်းလဲမှုများကို သိမ်းရန်",
        "publishpage": "စာမျက်နှာကို လွှင့်တင်ရန်",
        "publishchanges": "ပြောင်းလဲမှုများကို လွှင့်တင်ရန်",
        "preview": "နမူနာ",
index 9420fe4..bf9f083 100644 (file)
        "yourpasswordagain": "Têng phah bi̍t-bé:",
        "createacct-yourpasswordagain": "Khak-jīn bi̍t-bé",
        "createacct-yourpasswordagain-ph": "Koh phah chi̍t-pái bi̍t-bé",
-       "remembermypassword": "Tī chit ê liû-lám-khì kì góa ê teng-ji̍p chu-liāu.(siōng chē kì $1 {{PLURAL:$1|kang|kang}})",
        "userlogin-remembermypassword": "Kì-lo̍k goá teng-ji̍p--ê chu-liāu",
        "userlogin-signwithsecure": "用安全連線",
        "yourdomainname": "你的網域:",
index d7e8636..0eb17e9 100644 (file)
        "yourpasswordagain": "Gjenta passord",
        "createacct-yourpasswordagain": "Bekreft passord",
        "createacct-yourpasswordagain-ph": "Gjenta passordet",
-       "remembermypassword": "Husk meg på denne datamaskinen (i maks $1 {{PLURAL:$1|dag|dager}})",
        "userlogin-remembermypassword": "Hold meg innlogget",
        "userlogin-signwithsecure": "Logg inn med sikker tjener",
        "cannotloginnow-title": "Kan ikke logge inn nå",
        "changepassword-success": "Passordet ditt er endret!",
        "changepassword-throttled": "Du har foretatt for mange nylige innloggingsforsøk.\nVær vennlig å vente $1 før du prøver igjen.",
        "botpasswords": "Robotpassord",
-       "botpasswords-summary": "<em>Robotpassord</em> gir tilgang til en brukerkonto via API uten å bruke hovedpassordet til kontoen. Brukerrettighetene kan bli begrenset ved bruk av dette passordet.\n\nHvis du ikke vet om du vil benytte dette, er det sannsynlig at du ikke bør fylle det ut. Det skal ikke være nødvendig for andre personer å be deg om å fylle ut dette for å gi det til de.",
+       "botpasswords-summary": "<em>Robotpassord</em> gir tilgang til en brukerkonto via API uten å bruke hovedpassordet til kontoen. Brukerrettighetene kan bli begrenset ved bruk av dette passordet.\n\nHvis du ikke vet om du vil benytte dette, er det sannsynlig at du ikke bør fylle det ut. Det skal ikke være nødvendig for andre personer å be deg om å fylle ut dette for å gi det til dem.",
        "botpasswords-disabled": "Robotpassord er deaktivert.",
        "botpasswords-no-central-id": "For å bruke robotpassord må du være logget inn med en sentralisert konto.",
        "botpasswords-existing": "Eksisterende robotpassord",
        "passwordreset-emailsentusername": "Hvis det finnes en epostadresse knyttet til dette brukernavnet, vil en epost med informasjon om tilbakestilling av passord bli sendt.",
        "passwordreset-emailsent-capture2": "{{PLURAL:$1|E-post}} om passordtilbakestilling har blitt sendt. {{PLURAL:$1|Brukernavnet og passordet|Listen over brukernavn og passord}} vises under.",
        "passwordreset-emailerror-capture2": "Kunne ikke sende e-post til {{GENDER:$2|brukeren}}: $1 {{PLURAL:$3|Brukernavnet og passordet|Listen over brukernavn og passord}} vises under.",
+       "passwordreset-nocaller": "En bruker må angis",
+       "passwordreset-nosuchcaller": "Brukeren finnes ikke: $1",
+       "passwordreset-ignored": "Passordtilbakestillingen ble ikke håndtert. Har ingen leverandør blitt konfigurert?",
        "passwordreset-invalideamil": "Ugyldig e-postadresse",
        "passwordreset-nodata": "Verken et brukernavn eller en e-postadresse ble oppgitt",
        "changeemail": "Endre eller fjerne epostadresse",
        "savearticle": "Lagre siden",
        "savechanges": "Lagre endringer",
        "publishpage": "Publiser siden",
-       "publishchanges": "Publiser endringene",
+       "publishchanges": "Publiser endringer",
        "preview": "Forhåndsvisning",
        "showpreview": "Forhåndsvisning",
        "showdiff": "Vis endringer",
        "mergehistory-fail-bad-timestamp": "Tidsangivelsen er ugyldig.",
        "mergehistory-fail-invalid-source": "Kildesiden er ugyldig.",
        "mergehistory-fail-invalid-dest": "Målsiden er ugyldig.",
+       "mergehistory-fail-no-change": "Historieflettingen flettet ingen revisjoner. Vennligst sjekk siden og tidsparameterne igjen.",
        "mergehistory-fail-permission": "Utilstrekkelige tillatelser for å flette historikk.",
        "mergehistory-fail-self-merge": "Kilde- og målsiden er den samme.",
        "mergehistory-fail-timestamps-overlap": "Kilderevisjoner overlapper eller kommer etter målrevisjoner.",
        "grant-group-high-volume": "Utføre høyvolumaktivitet",
        "grant-group-customization": "Tilpasninger og innstillinger",
        "grant-group-administration": "Utføre administrative handlinger",
+       "grant-group-private-information": "Få tilgang til private data om deg",
        "grant-group-other": "Andre ting",
        "grant-blockusers": "Blokkere og avblokkere brukere",
        "grant-createaccount": "Opprette kontoer",
        "grant-highvolume": "Høy&shy;volum&shy;redigering",
        "grant-oversight": "Skjule brukere og undertrykke revisjoner",
        "grant-patrol": "Patruljere sideendringer",
+       "grant-privateinfo": "Få tilgang til privat informasjon",
        "grant-protect": "Beskytte og avbeskytte sider",
        "grant-rollback": "Tilbakestille side&shy;endringer",
        "grant-sendemail": "Sende e-post til andre brukere",
        "action-createpage": "opprette denne siden",
        "action-createtalk": "opprette denne diskusjonssiden",
        "action-createaccount": "opprette denne kontoen",
+       "action-autocreateaccount": "automatisk opprette denne eksterne brukerkontoen",
        "action-history": "se historikken til denne siden",
        "action-minoredit": "merke denne redigeringen som mindre",
        "action-move": "flytte denne siden",
        "action-applychangetags": "bruk merker sammen med dine endringer",
        "action-changetags": "legg til og fjern vilkårlige merker på individuelle revisjoner og loggposter",
        "action-deletechangetags": "slette tagger fra databasen",
+       "action-purge": "gjenoppfriske denne siden",
        "nchanges": "$1 {{PLURAL:$1|endring|endringer}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|siden forrige besøk}}",
        "enhancedrc-history": "historikk",
        "file-thumbnail-no": "Filnavnet begynner med <strong>$1</strong>.\nDet virker som om det er et bilde av redusert størrelse ''(miniatyrbilde)''.\nOm du har dette bildet i stor utgave, last opp det, eller endre filnavnet på denne filen.",
        "fileexists-forbidden": "En fil med dette navnet finnes fra før, og kan ikke erstattes.\nOm du fortsatt ønsker å laste opp filen, gå tilbake og last den opp under et nytt navn. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Ei fil med dette navnet finnes fra før i det delte fillageret.\nOm du fortsatt ønsker å laste opp filen, gå tilbake og last den opp under et nytt navn. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Opplastingen er et eksakt duplikat av følgende versjon av <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Opplastingen er et eksakt duplikat av {{PLURAL:$2|en eldre versjon|eldre versjoner}} av <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Denne filen er en dublett av følgende {{PLURAL:$1|fil|filer}}:",
        "file-deleted-duplicate": "En fil identisk med denne filen ([[:$1]]) har tidligere blitt slettet. Du bør sjekke denne filens slettehistorikk før du prøver å laste den opp på nytt.",
        "file-deleted-duplicate-notitle": "En annen fil identisk med denne filen har tidligere blitt slettet og tittelen har blitt fjernet. Du bør sjekke med noen som kan se på fjernede fildata å vurdere saken før filen lastes opp igjen.",
        "upload-scripted-pi-callback": "Det er ikke tillatt å laste opp en fil som inneholder et kjørbart XML-stilark.",
        "uploaded-script-svg": "Fant et skriptelement \"$1\" i den opplastede SVG-koden.",
        "uploaded-hostile-svg": "Fant usikker CSS i stilelementet til opplastet SVG-fil",
+       "uploaded-event-handler-on-svg": "Å sette event-handler-attributtene <code>$1=\"$2\"</code> tillates ikke i SVG-filer.",
        "uploaded-href-attribute-svg": "href-attributter i SVG-filer tillates kun for http://- eller https://-mål; fant <code>&lt;$1 $2=\"$3\"%gt;</code>.",
        "uploaded-href-unsafe-target-svg": "Fant href til usikre data: URI-mål <code>&lt;$1 $2=\"$3\"&gt;</code> i den opplastede SVG-filen.",
+       "uploaded-animate-svg": "Fant en «animate»-tagg som kan endre href, bruk attributtet «from» <code>&lt;$1 $2=\"$3\"&gt;</code> i den opplastede SVG-fila.",
+       "uploaded-setting-event-handler-svg": "Setting av event-handler-attributter er blokkert, fant <code>&lt;$1 $2=\"$3\"&gt;</code> i den opplastede SVG-fila.",
+       "uploaded-setting-href-svg": "Bruk av «set»-taggen for å legge til «href»-attributt til foreldreelementet er blokkert.",
+       "uploaded-wrong-setting-svg": "Bruk av «set»-taggen for å legge til et eksternt/data- eller skriptmål til attributter er blokkert. Fant <code>&lt;set to=\"$1\"&gt;</code> i den opplastede SVG-fila.",
+       "uploaded-setting-handler-svg": "SVG-er som setter «handler»-attributtet med remote/data/script er blokkert. Fant <code>$1=\"$2\"</code> i den opplastede SVG-fila.",
+       "uploaded-remote-url-svg": "SVG-er som setter et stilattributt med ekstern URL er blokkert. Fant <code>$1=\"$2\"</code> i den opplastede SVG-fila.",
+       "uploaded-image-filter-svg": "Fant bildefilter med URL: <code>&lt;$1 $2=\"$3\"&gt;</code> i den opplastede SVG-fila.",
        "uploadscriptednamespace": "Denne SVG-filen inneholder et ulovlig navnerom \"$1\"",
        "uploadinvalidxml": "XML-en i den opplastede filen kunne ikke tolkes.",
        "uploadvirus": "Denne filen inneholder virus! Detaljer: $1",
        "upload-too-many-redirects": "URL-en inneholdt for mange omdirigeringer",
        "upload-http-error": "En HTTP-feil oppstod: $1",
        "upload-copy-upload-invalid-domain": "Opplasting av kopier er ikke tilgjengelig fra dette domenet.",
+       "upload-foreign-cant-upload": "Denne wikien er ikke konfigurert til å laste opp filer til det forespurte eksterne fillageret.",
+       "upload-foreign-cant-load-config": "Lasting av konfigurasjonen for filopplastinger til det eksterne fillageret mislyktes.",
+       "upload-dialog-disabled": "Filopplastinger med denne dialogen er slått av for denne wikien.",
        "upload-dialog-title": "Last opp fil",
        "upload-dialog-button-cancel": "Avbryt",
        "upload-dialog-button-done": "Utført",
        "upload-dialog-button-upload": "Last opp",
        "upload-form-label-infoform-title": "Detaljer",
        "upload-form-label-infoform-name": "Navn",
+       "upload-form-label-infoform-name-tooltip": "En unik beskrivende tittel for fila, som vil brukes som filnavn. Du kan bruke vanlig språk med mellomrom. Ikke ta med filendelsen.",
        "upload-form-label-infoform-description": "Beskrivelse",
+       "upload-form-label-infoform-description-tooltip": "Beskriv kort alt som er bemerkelsesverdig med verket.\nFor bilder, nevn hovedtingene som avbildes, anledningen eller stedet.",
        "upload-form-label-usage-title": "Bruk",
        "upload-form-label-usage-filename": "Filnavn",
        "upload-form-label-own-work": "Dette er mitt eget verk",
        "uploadstash-badtoken": "Utføringen av handlingen feilet, kanskje fordi redigeringsrettighetene dine har utløpt. Prøv igjen.",
        "uploadstash-errclear": "Filene lot seg ikke fjerne.",
        "uploadstash-refresh": "Oppdater listen over filer",
+       "uploadstash-thumbnail": "vis miniatyrbilde",
+       "uploadstash-exception": "Kunne ikke lagre opplastingen i stashen ($1): «$2».",
        "invalid-chunk-offset": "Ugyldig delforskyvning",
        "img-auth-accessdenied": "Ingen tilgang",
        "img-auth-nopathinfo": "Manglende PATH_INFO.\nTjeneren din er ikke satt opp til å gi denne informasjonen.\nDen er kanskje CGI-basert og støtter ikke img_auth.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization Se bildeautorisasjon.",
        "filerevert-submit": "Tilbakestill",
        "filerevert-success": "'''[[Media:$1|$1]]''' ble tilbakestilt til [$4 versjonen à $2, $3].",
        "filerevert-badversion": "Det er ingen tidligere lokal versjon av denne filen med det gitte tidstrykket.",
+       "filerevert-identical": "Den nåværende versjonen av fila er allerede identisk med den valgte.",
        "filedelete": "Slett $1",
        "filedelete-legend": "Slett fil",
        "filedelete-intro": "Du er i ferd med å slette filen '''[[Media:$1|$1]]''' sammen med hele dens historikk.",
        "apihelp": "API hjelp",
        "apihelp-no-such-module": "Modulen «$1» ikke funnet.",
        "apisandbox": "API-sandkasse",
+       "apisandbox-jsonly": "JavaScript kreves for å bruke API-sandkassa.",
        "apisandbox-api-disabled": "API er deaktivert på dette nettstedet.",
        "apisandbox-intro": "Bruk denne siden for å eksperimentere med <strong>MediaWiki webtjeneste-APIet</strong>.\nSjekk [[mw:API:Main page|API-dokumentasjonen]] for mer informasjon om bruk av APIet. Eksempel: [https://www.mediawiki.org/wiki/API#A_simple_example hente innholdet til en hovedside]. Velg en handling for å se flere eksempler.\n\nMerk at du kan utføre handlinger her som fører til endringer på wikien.",
+       "apisandbox-fullscreen": "Utvid panelet",
+       "apisandbox-fullscreen-tooltip": "Utvid sandkassepanelet så det dekker nettleservinduet.",
+       "apisandbox-unfullscreen": "Vis siden",
+       "apisandbox-unfullscreen-tooltip": "Reduser størrelsen på sandkassepanelet, så MediaWikis navigasjonslenker er tilgjengelige.",
        "apisandbox-submit": "Foreta en forespørsel",
        "apisandbox-reset": "Tilbakestill",
+       "apisandbox-retry": "Prøv igjen",
+       "apisandbox-loading": "Laster informasjon for API-modulen «$1»...",
+       "apisandbox-load-error": "En feil oppsto under lasting av informasjon for API-modulen «$1»: $2",
+       "apisandbox-no-parameters": "Denne API-modulen har ingen parametre.",
+       "apisandbox-helpurls": "Hjelpelenker",
        "apisandbox-examples": "Eksempler",
+       "apisandbox-dynamic-parameters": "Ekstra parametre",
+       "apisandbox-dynamic-parameters-add-label": "Legg til parameter:",
+       "apisandbox-dynamic-parameters-add-placeholder": "Parameternavn",
+       "apisandbox-dynamic-error-exists": "En parameter med navn «$1» finnes fra før.",
+       "apisandbox-deprecated-parameters": "Utgåtte parametre",
+       "apisandbox-fetch-token": "Fyll inn nøkkelen automatisk",
+       "apisandbox-submit-invalid-fields-title": "Noen felt er ugyldige",
+       "apisandbox-submit-invalid-fields-message": "Fiks de markerte feltene og prøv igjen.",
        "apisandbox-results": "Resultater",
+       "apisandbox-sending-request": "Sender API-forespørsel...",
+       "apisandbox-loading-results": "Mottar API-resultater...",
+       "apisandbox-results-error": "En feil oppsto under lasting av API-spørringssvaret: $1.",
        "apisandbox-request-url-label": "Forespurt URL:",
        "apisandbox-request-time": "Forespørselstid: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-results-fixtoken": "Fiks nøkkelen og send på nytt",
+       "apisandbox-results-fixtoken-fail": "Henting av nøkkelen «$1» mislyktes.",
+       "apisandbox-alert-page": "Felter på denne siden er ugyldige.",
+       "apisandbox-alert-field": "Verdien til dette feltet er ugyldig.",
        "booksources": "Bokkilder",
        "booksources-search-legend": "Søk etter bokkilder",
        "booksources-search": "Søk",
index 9636b64..fd3ccea 100644 (file)
        "faqpage": "Project:धैरै सोधिएका प्रश्नहरु",
        "actions": "कार्यहरु",
        "namespaces": "नेमस्पेस",
-       "variants": "बहà¥\81रà¥\81पहरà¥\81",
+       "variants": "बहà¥\81रà¥\81पहरà¥\82",
        "navigation-heading": "नेविगेशन मेनू",
        "errorpagetitle": "त्रुटि",
        "returnto": "$1 मा फर्कनुहोस् ।",
        "yourpasswordagain": "पासवर्ड फेरि टाईप गर्नुहोस्",
        "createacct-yourpasswordagain": "पासवर्ड निश्चित गर्नुहोस्",
        "createacct-yourpasswordagain-ph": "फेरि पासवर्ड लेख्नुहोस्",
-       "remembermypassword": "यो कम्प्युटरमा मेरो प्रवेश याद राख्ने (धेरैमा $1 {{PLURAL:$1|दिन|दिनहरू}})",
        "userlogin-remembermypassword": "मलाई प्रवेश गराइराख्ने",
        "userlogin-signwithsecure": "सुक्षित जडान प्रयोग गर्ने",
        "yourdomainname": "तपाईंको ज्ञानक्षेत्र(डोमेन):",
        "image_tip": "इम्बेडेड(जडान गरिएको) फाइल",
        "media_sample": "उदाहरण.ogg",
        "media_tip": "फाइल लिङ्क",
-       "sig_tip": "तपाà¤\88à¤\81को समयछाप सहितको दस्तखत",
+       "sig_tip": "तपाà¤\88à¤\82को समयछाप सहितको दस्तखत",
        "hr_tip": "क्षितिजिय रेखा (कम प्रयोग गर्नुहोस्)",
        "summary": "सारांश:",
        "subject": "विषय/शीर्षक:",
        "showpreview": "पूर्वालोकन देखाउनुहोस्",
        "showdiff": "परिवर्तन देखाउनुहोस्",
        "blankarticle": "<strong>चेतावनी:</strong> तपाईं एउटा खालि पृष्ठको निर्माण गर्दै हुनुहुन्छ।\nयदि तपाईं \"{{int:savearticle}}\" लाई पुनः थिच्नुहुन्छ भने पृष्ठ बिना कुनै सामग्री नै निर्मित गरिनेछ।",
-       "anoneditwarning": "<strong>à¤\9aà¥\87तावनà¥\80:</strong> à¤¤à¤ªà¤¾à¤\88à¤\81लà¥\87 à¤ªà¥\8dरवà¥\87श à¤\97रà¥\8dनà¥\81 à¤­à¤\8fà¤\95à¥\8b à¤\9bà¥\88न à¥¤ à¤¤à¤ªà¤¾à¤\88à¤\81à¤\95à¥\8b à¤\86à¤\87पि à¤ à¥\87à¤\97ाना à¤ªà¥\83षà¥\8dठ à¤¸à¤®à¥\8dपादन à¤\87तिहासमा à¤¦à¤°à¥\8dता à¤\97रिनà¥\87 à¤\9b à¤° à¤¯à¥\8b à¤¸à¤¬à¥\88लà¥\87 à¤¹à¥\87रà¥\8dन à¤¸à¤\95à¥\8dà¤\9bन à¥¤ à¤¯à¤¦à¤¿ à¤¤à¤ªà¤¾à¤\88à¤\82 <strong>[$1 à¤²à¤\97à¤\88न]</strong> à¤µà¤¾ <strong>[$2 à¤¨à¤¯à¤¾à¤\81 à¤\96ाता à¤¬à¤¨à¤¾à¤\89नà¥\87] à¤\97रà¥\8dनà¥\81भयà¥\8b à¤­à¤¨à¥\87 à¤¤à¤ªà¤¾à¤\88à¤\82दà¥\8dवारा à¤\97रिà¤\8fà¤\95à¥\8b à¤¸à¤®à¥\8dपादन à¤¤à¤ªà¤¾à¤\88à¤\82à¤\95à¥\8b à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dतानाममा à¤\9cà¥\8bडिनà¥\87à¤\9b।",
+       "anoneditwarning": "<strong>à¤\9aà¥\87तावनà¥\80:</strong> à¤¤à¤ªà¤¾à¤\88à¤\82लà¥\87 à¤ªà¥\8dरवà¥\87श à¤\97रà¥\8dनà¥\81 à¤­à¤\8fà¤\95à¥\8b à¤\9bà¥\88न à¥¤ à¤¤à¤ªà¤¾à¤\88à¤\82à¤\95à¥\8b à¤\86à¤\87पि à¤ à¥\87à¤\97ाना à¤ªà¥\83षà¥\8dठ à¤¸à¤®à¥\8dपादन à¤\87तिहासमा à¤¦à¤°à¥\8dता à¤\97रिनà¥\87 à¤\9b à¤° à¤¯à¥\8b à¤¸à¤¬à¥\88लà¥\87 à¤¹à¥\87रà¥\8dन à¤¸à¤\95à¥\8dà¤\9bनà¥\8d à¥¤ à¤¯à¤¦à¤¿ à¤¤à¤ªà¤¾à¤\88à¤\82 <strong>[$1 à¤²à¤\97à¤\88न]</strong> à¤µà¤¾ <strong>[$2 à¤¨à¤¯à¤¾à¤\81 à¤\96ाता à¤¬à¤¨à¤¾à¤\89नà¥\87] à¤\97रà¥\8dनà¥\81भयà¥\8b à¤­à¤¨à¥\87 à¤¤à¤ªà¤¾à¤\88à¤\82दà¥\8dवारा à¤\97रिà¤\8fà¤\95à¥\8b à¤¸à¤®à¥\8dपादन à¤¤à¤ªà¤¾à¤\88à¤\82à¤\95à¥\8b à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dतानाममा à¤\9cà¥\8bडिनà¥\87à¤\9b ।",
        "anonpreviewwarning": "''तपाईंले प्रवेश गर्नु भएको छैन। संग्रह (Save) गरेको खण्डमा पृष्ठको इतिहासमा तपाईंको IP ठेगाना अंकित गरिनेछ।''",
        "missingsummary": "'''यादगर्नुहोस् :''' तपाईंले सम्पादन सारांश दिनुभएको छैन ।\nयदि तपाईंले \"{{int:savearticle}}\"  थिच्नुभयो भने , सारांश बिना नै संग्रहित गरिने छ ।",
        "selfredirect": "<strong>चेतावनी:</strong> तपाईं यस पृष्ठलाई आफुमा पुनः निर्देशित गर्दै हुनुहुन्छ।\nहुनसक्छ तपाईं अनुप्रेषितको लागि गलत लक्ष्य निर्दिष्ट गर्दै हुनुहुन्छ, वा गलत पृष्ठको सम्पादन गर्दै हुनुहुन्छ।\nतपाईं पुनः एकपटक \"{{int:savearticle}}\" क्लिक गर्नुहुन्छ, पुनः निर्देशित त्यसै पनि बनाइनेछ।",
        "diff-multi-manyusers": "($2 {{PLURAL:$2|भन्दा अधिक प्रयोगकर्ता|भन्दा अधिक प्रयोगकर्ताहरू}}द्वारा {{PLURAL:$1|एउटा मध्यवर्ती संशोधन|$1 मध्यवर्ती संशोधनहरू}} नदेखाइएको)",
        "difference-missing-revision": "यस अन्तर {{PLURAL:$2|को एक अवतरण|को $2 अवतरण}} ($1)  {{PLURAL:$2|भेटिएन|खोज्न सकिएन}}।\n\nयो सामान्य रूपमा एउटा हताइएको पृष्ठको अवतरणहरूमा अन्तर खोज्दा हुन्छ । अधिक जानकारी [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटाइएको लग]मा हेर्न सकिन्छ।",
        "searchresults": "खोज नतिजाहरू",
-       "searchresults-title": " \"$1\"à¤\95à¥\8b à¤²à¤¾à¤\97ि à¤\96à¥\8bà¤\9c à¤¨à¤¤à¤¿à¤\9cाहरà¥\81",
+       "searchresults-title": " \"$1\"à¤\95à¥\8b à¤²à¤¾à¤\97ि à¤\96à¥\8bà¤\9c à¤¨à¤¤à¤¿à¤\9cाहरà¥\82",
        "titlematches": "पृष्ठ शिर्षक मिल्छ",
        "textmatches": "पृष्ठ पाठ मिल्छ",
        "notextmatches": "अक्षरस् पेज भेटिएन",
        "next-page": "अर्को पृष्ठ",
        "prevn-title": "पहिलेको  $1 {{PLURAL:$1|नतिजा|नतिजाहरु}}",
        "nextn-title": "यस पछिको $1 {{PLURAL:$1|नतिजा |नतिजाहरु}}",
-       "shown-title": "दà¥\87à¤\96ाà¤\89नà¥\87 $1 {{PLURAL:$1|नतिà¤\9cा|नतिà¤\9cाहरà¥\81}} प्रति पृष्ठ",
+       "shown-title": "दà¥\87à¤\96ाà¤\89नà¥\87 $1 {{PLURAL:$1|नतिà¤\9cा|नतिà¤\9cाहरà¥\82}} प्रति पृष्ठ",
        "viewprevnext": "हेर्नुहोस् ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "''' \"[[:$1]]\" नाम गरेको पृष्ठ  यो विकीमा रहेको छ'''",
        "searchmenu-new": "<strong>\"[[:$1]]\" पृष्ठ यस विकिमा बनाउनुहोस्!</strong> {{PLURAL:$2|0=|तपाईंले खोज गरी भटिएको पृष्ठ पनि मिलान गर्नुहोस्।|तपाईंको खोज परिणाम पनि हेर्नुहोस।}}",
        "showingresults": "देखाउँदै  {{PLURAL:$1|'''१''' नतिजा|'''$1''' नतिजाहरू }} , #'''$2''' बाट सुरुहुने ।",
        "showingresultsinrange": "देखाई रहेको छ{{PLURAL:$1|<strong>1</strong> result|<strong>$1</strong> परिणाम}} सम्म पहुँच  #<strong>$2</strong> देखि #<strong>$3</strong> मा।",
        "search-showingresults": "{{PLURAL:$4|<strong>$3</strong> मा बाट <strong>$1</strong> परिणाम|<strong>$3</strong> मा बाट परिणाम <strong>$1 - $2</strong>}}",
-       "search-nonefound": "तपाà¤\88à¤\81को क्वेरीसँग मेल खाने नतिजाहरू भेटिएनन्",
+       "search-nonefound": "तपाà¤\88à¤\82को क्वेरीसँग मेल खाने नतिजाहरू भेटिएनन्",
        "powersearch-legend": "उन्नत खोज",
        "powersearch-ns": "नेमस्पेसेजहरूमा खोज्ने :",
        "powersearch-togglelabel": "जाँच्ने :",
        "recentchangeslinked-feed": "सम्बन्धित परिवर्तनहरू",
        "recentchangeslinked-toolbox": "सम्बन्धित परिवर्तनहरू",
        "recentchangeslinked-title": "\"$1\" सँग सम्बन्धित परिवर्तन",
-       "recentchangeslinked-summary": "यो सूची निर्दिष्ट पृष्ठ (वा निर्दिष्ट श्रेणी)सित जोडिएका भर्खरै परिवर्तन भएका पृष्ठको  हो। [[Special:Watchlist|तपाईँको निगरानी सूची]]का पृष्ठहरू <strong>गाढा अक्षरमा</strong> छन्।",
+       "recentchangeslinked-summary": "यो सूची निर्दिष्ट पृष्ठ (वा निर्दिष्ट श्रेणी)सित जोडिएका भर्खरै परिवर्तन भएका पृष्ठको  हो । [[Special:Watchlist|तपाईंको निगरानी सूची]]का पृष्ठहरू <strong>गाढा अक्षरमा</strong> छन् ।",
        "recentchangeslinked-page": "पृष्ठ नाम:",
        "recentchangeslinked-to": "यसको सट्टा यो पृष्ठसँग जोडिएका पृष्ठहरूको परिवर्तन देखाउने",
        "upload": "फाइल उर्ध्वभरण",
        "filehist-dimensions": "आकारहरू",
        "filehist-filesize": "फाइल आकार",
        "filehist-comment": "टिप्पणी",
-       "imagelinks": "फाà¤\87लà¤\95à¥\8b à¤ªà¥\8dरयà¥\8bà¤\97हरà¥\81",
+       "imagelinks": "फाà¤\87लà¤\95à¥\8b à¤ªà¥\8dरयà¥\8bà¤\97हरà¥\82",
        "linkstoimage": "यस फाइलमा निम्न{{PLURAL:$1|पृष्ठ जोडिन्छ|$1 पृष्ठहरू जोडिन्छन्}}:",
        "linkstoimage-more": "$1 भन्दा अधिक {{PLURAL:$1|पृष्ठ लिङ्क|पृष्ठ लिङ्कहरू}} यस फाइलसँग जोडिएको छ। \nनिम्नलिखित सूची फाइलसँग {{PLURAL:$1|पहिलो पृष्ठ लिङ्क|पहिलो $1 पृष्ठ लिङ्कहरू}} जोडिने देखाउँछ।\n[[Special:WhatLinksHere/$2|पूर्ण सूची]] पनि उपलब्ध छ।",
        "nolinkstoimage": "यो फाईलसंग लिंकभएको कुनै पृष्ठ छैन.",
        "duplicatesoffile": "निम्नलिखित {{PLURAL:$1|फाइलको प्रतिलिपि हो|$1 फाइलहरूको प्रतिलिपि हो}} ([[Special:FileDuplicateSearch/$2|अधिक जानकारीहरू]]):",
        "sharedupload": "यो फाइल $1 को हो र अन्य परियोजनामा प्रयोग गरिएको हुनसक्छ।",
        "sharedupload-desc-there": "यो फाइल $1 बाट हो र अन्य परियोजनाहरू द्वारा पनि प्रयोग गर्न सकिनेछ। अधिक जानकारीको लागि कृपया [$2 फाइल विवरण पृष्ठ] हेर्नुहोस।",
-       "sharedupload-desc-here": "यà¥\8b à¤«à¤¾à¤\87ल $1 à¤¬à¤¾à¤\9f à¤¹à¥\8b à¤° à¤\85नà¥\8dय à¤ªà¤°à¤¿à¤¯à¥\8bà¤\9cनाहरà¥\82 à¤¦à¥\8dवारा à¤ªà¤¨à¤¿ à¤ªà¥\8dरयà¥\8bà¤\97 à¤\97रà¥\8dन à¤¸à¤\95िनà¥\8dà¤\9b। \nतà¥\8dयहाà¤\81 à¤¨à¥\87र à¤¯à¤¸à¤\95à¥\8b [$2 à¤«à¤¼à¤¾à¤\87ल à¤µà¤¿à¤µà¤°à¤£ à¤ªà¥\83षà¥\8dठ]मा à¤°à¤¹à¥\87à¤\95à¥\8b à¤µà¤¿à¤µà¤°à¤£ à¤¤à¤² à¤¦à¤¿à¤\87à¤\8fà¤\95à¥\8b à¤\9b।",
+       "sharedupload-desc-here": "यो फाइल $1 बाट हो र अन्य परियोजनाहरू द्वारा पनि प्रयोग गर्न सकिन्छ। \nत्यहाँ नेर यसको [$2 फाइल विवरण पृष्ठ]मा रहेको विवरण तल दिइएको छ।",
        "sharedupload-desc-edit": "यो फाइल $1 बाट हो र अन्य परियोजनाहरू द्वारा पनि प्रयोग गर्न सकिन्छ। \nशायद तपाईं [$2 त्यहाँ यसको फाइल विवरण पृष्ठ]लाई सम्पादित गर्न चाहनुहुन्छ।",
        "sharedupload-desc-create": "यो फाइल $1 बाट हो र अन्य परियोजनाहरू द्वारा पनि प्रयोग गर्न सकिन्छ। \nशायद तपाईं [$2 त्यहाँ यसको फाइल विवरण पृष्ठ]लाई सम्पादित गर्न चाहनुहुन्छ।",
        "filepage-nofile": "यस नामको फाइल छैन।",
        "shared-repo-from": " $1 बाट",
        "shared-repo": "एल साझा भण्डार",
        "shared-repo-name-wikimediacommons": "विकिमीडिया कमन्स",
-       "upload-disallowed-here": "तपाà¤\88à¤\81ले यो फाइल अधिलेखन गर्न सक्नुहुन्न ।",
+       "upload-disallowed-here": "तपाà¤\88à¤\82ले यो फाइल अधिलेखन गर्न सक्नुहुन्न ।",
        "filerevert": "पूर्वस्थिति $1 मा फर्काउने",
        "filerevert-legend": " फाइल पूर्वस्थितीमा फर्काउने",
        "filerevert-intro": "तपाईं <strong>[[Media:$1|$1]]</strong>लाई [$4 $2 मा $3 बजेको अवतरण] लाई पूर्ववत गर्दै हुनुहुन्छ।",
        "javascripttest": "JavaScript जाँच गरिदै",
        "javascripttest-pagetext-unknownaction": "अज्ञात कारवाही \"$1\" ।",
        "javascripttest-qunit-intro": "mediawiki.org मा [$1 जाँचको कागजात] हेर्नुहोस् ।",
-       "tooltip-pt-userpage": "तपाईंको प्रयोगकर्ता पृष्ठ",
+       "tooltip-pt-userpage": "{{GENDER:| तपाईंको प्रयोगकर्ता}} पृष्ठ",
        "tooltip-pt-anonuserpage": "तपाईँ जुन IP ठेगानाको रुपमा सम्पादन गर्दै हुनुहुन्छ , त्यसको प्रयोगकर्ता पृष्ठ निम्न छ :",
-       "tooltip-pt-mytalk": "तपाईंको वार्ता पृष्ठ",
+       "tooltip-pt-mytalk": "{{GENDER:|तपाईंको}} वार्ता पृष्ठ",
        "tooltip-pt-anontalk": "यो IP ठेगानाबाट गरिएका सम्पादनका बारेमा बार्तालाप",
-       "tooltip-pt-preferences": "तपाईंका अभिरुचिहरू",
+       "tooltip-pt-preferences": "{{GENDER:|तपाईंका}} अभिरुचिहरू",
        "tooltip-pt-watchlist": "पृष्ठहरूको सूची जसका परिवर्तनहरूलाई तपाईँले निगरानी गरिरहनु भएको छ",
-       "tooltip-pt-mycontris": "तपाईंका योगदानहरूको सूची",
-       "tooltip-pt-login": "तपाà¤\88à¤\81लाà¤\88 à¤ªà¥\8dरवà¥\87शगर्न सुझाव दिइन्छ ; तर यो जरुरी भने छैन",
+       "tooltip-pt-mycontris": "{{GENDER:|तपाईंका}} योगदानहरूको सूची",
+       "tooltip-pt-login": "तपाà¤\88à¤\82लाà¤\88 à¤ªà¥\8dरवà¥\87स गर्न सुझाव दिइन्छ ; तर यो जरुरी भने छैन",
        "tooltip-pt-logout": "निर्गमन (लग आउट) गर्नुहोस्",
-       "tooltip-pt-createaccount": "तपाईंलाई खाता बनाउन र लग इन गर्न हामि प्रोत्साहित गर्छौ; तथापि, यो अनिवार्य भने छैन।",
+       "tooltip-pt-createaccount": "तपाईंलाई खाता बनाउन र लग इन गर्न हामि प्रोत्साहित गर्छौ; तथापि, यो अनिवार्य भने छैन ।",
        "tooltip-ca-talk": "सामग्री पृष्ठबारेमा छलफल",
        "tooltip-ca-edit": "यो पृष्ठ सम्पादन गर्ने",
        "tooltip-ca-addsection": "नयाँ खण्ड सुरु गर्नुहोस्",
        "tooltip-ca-viewsource": "यो पृष्ठ सुरक्षित गरिएको छ। यसको श्रोत हेर्न सक्नुहुन्छ।",
-       "tooltip-ca-history": "यस à¤ªà¥\83षà¥\8dठà¤\95à¥\8b à¤ªà¤¹à¤¿à¤²à¥\87à¤\95ा à¤ªà¥\81नरावलà¥\8bà¤\95नहरà¥\81",
+       "tooltip-ca-history": "यस à¤ªà¥\83षà¥\8dठा à¤ªà¤¹à¤¿à¤²à¥\87à¤\95ा à¤ªà¥\81नरावलà¥\8bà¤\95नहरà¥\82",
        "tooltip-ca-protect": "यो पृष्ठलाई संरक्षित गर्नुहोस्",
        "tooltip-ca-unprotect": "यस पृष्ठको सुरक्षा परिवर्तन गर्ने",
        "tooltip-ca-delete": "यो पृष्ठ मेटाउनुहोस्",
        "tooltip-ca-undelete": "मेटिएको भए पनि यो पृष्ठको सम्पादनहरू पुन:प्राप्त गर्नुहोस्",
        "tooltip-ca-move": "यो पृष्ठलाई सार्नुहोस्",
-       "tooltip-ca-watch": "यà¥\8b à¤ªà¥\83षà¥\8dठलाà¤\88 à¤¤à¤ªà¤¾à¤\88à¤\81को अवलोकनसूचीमा थप्नुहोस्",
+       "tooltip-ca-watch": "यà¥\8b à¤ªà¥\83षà¥\8dठलाà¤\88 à¤¤à¤ªà¤¾à¤\88à¤\82को अवलोकनसूचीमा थप्नुहोस्",
        "tooltip-ca-unwatch": "यो पृष्ठलाई तपाईँको अवलोकनसूचीबाट हटाउनुहोस्",
        "tooltip-search": "{{SITENAME}} मा खोज्नुहोस्",
        "tooltip-search-go": "यदि यो नामको पृष्ठ रहेको छ भने त्यसमा जाने",
        "tooltip-search-fulltext": "यो पाठको लागि पृष्ठहरू खोज्नुहोस्",
        "tooltip-p-logo": "मुख्य पृष्ठ",
        "tooltip-n-mainpage": "मुख्य पृष्ठमा जाने",
-       "tooltip-n-mainpage-description": "मà¥\81à¤\96à¥\8dय à¤ªà¥\83षà¥\8dठमा à¤\9cानà¥\81हà¥\8bà¥\8dसà¥\8d",
-       "tooltip-n-portal": "à¤\86यà¥\8bà¤\9cनाà¤\95ा à¤¬à¤¾à¤°à¥\87मा, à¤¤à¤ªà¤¾à¤\88à¤\81 के गर्न सक्नुहुन्छ, सामग्री कहाँ भेट्टाउने",
+       "tooltip-n-mainpage-description": "मुख्य पृष्ठमा जानुहोस्",
+       "tooltip-n-portal": "à¤\86यà¥\8bà¤\9cनाà¤\95ा à¤¬à¤¾à¤°à¥\87मा, à¤¤à¤ªà¤¾à¤\88à¤\82 के गर्न सक्नुहुन्छ, सामग्री कहाँ भेट्टाउने",
        "tooltip-n-currentevents": "हालैको घटनाको बारेमा पृष्ठभूमि जानकारी पत्ता लगाउनुहोस्",
-       "tooltip-n-recentchanges": "विà¤\95िमा à¤\97रिà¤\8fà¤\95ा à¤¹à¤¾à¤²à¥\88à¤\95ा à¤ªà¤°à¤¿à¤µà¤°à¥\8dतनहरà¥\81को सूची",
-       "tooltip-n-randompage": "à¤\9cà¥\81न à¤\95à¥\81नà¥\88 पृष्ठ खोल्ने",
+       "tooltip-n-recentchanges": "विà¤\95िमा à¤\97रिà¤\8fà¤\95ा à¤¹à¤¾à¤²à¥\88à¤\95ा à¤ªà¤°à¤¿à¤µà¤°à¥\8dतनहरà¥\82को सूची",
+       "tooltip-n-randompage": "à¤\95à¥\81नà¥\88 à¤\8fà¤\95 पृष्ठ खोल्ने",
        "tooltip-n-help": "पत्तालगाउनु पर्ने स्थान",
        "tooltip-t-whatlinkshere": "यो सँग जोडिएका सबै विकि पृष्ठहरूको सूची",
        "tooltip-t-recentchangeslinked": "यस पृष्ठमा जोडिएका पृष्ठहरूमा हालैको परिवर्तन",
        "tooltip-ca-nstab-help": "सहायता पृष्ठ हेर्नुहोस्",
        "tooltip-ca-nstab-category": "श्रेणी पृष्ठ हेर्ने",
        "tooltip-minoredit": "यसलाई सामान्य सम्पादनको रुपमा चिनो लगाउने",
-       "tooltip-save": "तपाà¤\88à¤\81लà¥\87 à¤\97रà¥\87à¤\95ा à¤ªà¤°à¤¿à¤µà¤°à¥\8dतनहरà¥\82 à¤¸à¤\82ग्रह गर्नुहोस्",
-       "tooltip-preview": "तपाà¤\88à¤\81à¤\95à¥\8b à¤ªà¤°à¤¿à¤µà¤°à¥\8dतनà¤\95à¥\8b à¤ªà¥\82रà¥\8dवरà¥\82प , à¤\95à¥\83पया à¤¸à¤\82ग्रह गर्नु अघि यो प्रयोग गर्नुहोला !",
-       "tooltip-diff": "तपाà¤\88à¤\81ले पाठमा के के परिवर्तन गर्नुभयो भनेर देखाउने",
+       "tooltip-save": "तपाà¤\88à¤\82à¤\95ा à¤ªà¤°à¤¿à¤µà¤°à¥\8dतनहरà¥\82 à¤¸à¤\99à¥\8dग्रह गर्नुहोस्",
+       "tooltip-preview": "तपाà¤\88à¤\82à¤\95à¥\8b à¤ªà¤°à¤¿à¤µà¤°à¥\8dतनà¤\95à¥\8b à¤ªà¥\82रà¥\8dवरà¥\82प , à¤\95à¥\83पया à¤¸à¤\99à¥\8dग्रह गर्नु अघि यो प्रयोग गर्नुहोला !",
+       "tooltip-diff": "तपाà¤\88à¤\82ले पाठमा के के परिवर्तन गर्नुभयो भनेर देखाउने",
        "tooltip-compareselectedversions": "यस पृष्ठको छानिएका दुई पुनरावलोकन बीच फरक हेर्नुहोस्",
        "tooltip-watch": "यो पृष्ठलाई तपाईँको अवलोकनसूचीमा थप्नुहोस्",
        "tooltip-watchlistedit-normal-submit": "शीर्षकहरू हटाउने",
        "widthheight": "$1 × $2",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|पृष्ठ|पृष्ठहरू}}",
        "file-info": "फाइल आकार: $1, MIME प्रकार: $2",
-       "file-info-size": "$1 Ã\97 $2 à¤ªà¤¿à¤\95à¥\8dसà¥\87लहरà¥\81, फाइल आकार: $3, MIME प्रकार: $4",
+       "file-info-size": "$1 Ã\97 $2 à¤ªà¤¿à¤\95à¥\8dसà¥\87लहरà¥\82, फाइल आकार: $3, MIME प्रकार: $4",
        "file-info-size-pages": "$1 × $2 पिक्सेलहरू, फाइल आकार: $3, MIME प्रकार: $4, $5 {{PLURAL:$5|पृष्ठ|पृष्ठहरू}}",
        "file-nohires": "उच्च रिजोल्युशन अनुपलब्ध",
        "svg-long-desc": "SVG फाइल,साधारण $1 × $2 पिक्सेलहरु, फाइल आकार: $3",
        "svg-long-error": "अमान्य एसभिजी फाइल: $1",
        "show-big-image": "मूल फाइल",
        "show-big-image-preview": "यस पूर्व रुपको आकार: $1।",
-       "show-big-image-other": "à¤\85रà¥\81 {{PLURAL:$2|resolution|रिà¤\9cà¥\8bलà¥\8dयà¥\81शनहरà¥\81}}: $1।",
+       "show-big-image-other": "à¤\85रà¥\81 {{PLURAL:$2|resolution|रिà¤\9cà¥\8bलà¥\8dयà¥\81शनहरà¥\82}}: $1।",
        "show-big-image-size": "$1 × $2 पिक्सल",
        "file-info-gif-looped": "चकृय गरिएको",
        "file-info-gif-frames": "$1 {{PLURAL:$1|फ्रेम|फ्रेमहरु}}",
        "yesterday-at": "हिजो $1मा",
        "bad_image_list": "(* बाट शुरु हुने पंक्ति)को  विषय सूची मात्र मान्य छ।  पंक्तिको पहिलो लिङ्क नराम्रो फाइलसित लिङ्क हुनैपर्छ । एउटै पंक्तिमा कुनै पछिबाट हुने लिंकलाई अपवाद मानिनेछ अर्थात् जुन पृष्ठमा फाइल इन-लाइन हुनसक्छ।",
        "metadata": "मेटाडेटा",
-       "metadata-help": "यस à¤«à¤¾à¤\87लमा à¤\85तिरिà¤\95à¥\8dत à¤\9cानà¤\95ारà¥\80हरà¥\81 à¤\9bनà¥\8d, à¤¯à¤¸à¤²à¤¾à¤\88 à¤¬à¤¨à¤¾à¤\89न à¤¸à¤®à¥\8dभवतà¤\83 à¤¡à¤¿à¤\9cिà¤\9fल à¤\95à¥\8dयामà¥\87रा à¤\85थवा à¤¸à¥\8dà¤\95à¥\8dयानर à¤ªà¥\8dरयà¥\8bà¤\97 à¤\97रिà¤\8fà¤\95à¥\8b à¤¹à¥\81नà¥\81परà¥\8dà¤\9b। à¤¯à¤¦à¤¿ à¤¯à¤¸ à¤«à¤¾à¤\87ललाà¤\88 à¤®à¥\82ल à¤\85वसà¥\8dथाबाà¤\9f à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\97रिà¤\8fà¤\95à¥\8b à¤¹à¥\8b à¤­à¤¨à¥\87à¤\82  यस फाइलले  सम्पूर्ण विवरण प्रतिबिम्बित गर्न सक्नेछैन ।",
+       "metadata-help": "यस à¤«à¤¾à¤\87लमा à¤\85तिरिà¤\95à¥\8dत à¤\9cानà¤\95ारà¥\80हरà¥\82 à¤\9bनà¥\8d, à¤¯à¤¸à¤²à¤¾à¤\88 à¤¬à¤¨à¤¾à¤\89न à¤¸à¤®à¥\8dभवतà¤\83 à¤¡à¤¿à¤\9cिà¤\9fल à¤\95à¥\8dयामà¥\87रा à¤\85थवा à¤¸à¥\8dà¤\95à¥\8dयानर à¤ªà¥\8dरयà¥\8bà¤\97 à¤\97रिà¤\8fà¤\95à¥\8b à¤¹à¥\81नà¥\81परà¥\8dà¤\9b à¥¤ à¤¯à¤¦à¤¿ à¤¯à¤¸ à¤«à¤¾à¤\87ललाà¤\88 à¤®à¥\82ल à¤\85वसà¥\8dथाबाà¤\9f à¤ªà¤°à¤¿à¤µà¤°à¥\8dतन à¤\97रिà¤\8fà¤\95à¥\8b à¤¹à¥\8b à¤­à¤¨à¥\87  यस फाइलले  सम्पूर्ण विवरण प्रतिबिम्बित गर्न सक्नेछैन ।",
        "metadata-expand": "लामो विबरण हेर्ने",
        "metadata-collapse": "लामो विवरण लुकाउने",
-       "metadata-fields": "Image metadata fields listed in this message will be included on image page display when the metadata table is collapsed.\nOthers will be hidden by default.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "metadata-fields": "मेटाडाटा तालिकालाई लघुरूप गरियो भने यस सन्देशमा सूचीबद्ध इएक्सआयएफ मेटाडाटा जानकारिहरू छवि प्रदर्शित हुने बेला सम्मिलित गरिने छ ।\nअन्य डिफल्ट रूपसँग लुकिरहने छ ।\n* make \n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "exif-imagewidth": "चौडाइ",
        "exif-imagelength": "उचाई",
        "exif-bitspersample": "घटक प्रति बिट्स",
index 7d99e09..442c685 100644 (file)
        "yourpasswordagain": "Geef uw wachtwoord opnieuw in:",
        "createacct-yourpasswordagain": "Bevestig wachtwoord",
        "createacct-yourpasswordagain-ph": "Geef het wachtwoord opnieuw in",
-       "remembermypassword": "Aanmeldgegevens onthouden (maximaal $1 {{PLURAL:$1|dag|dagen}})",
        "userlogin-remembermypassword": "Aangemeld blijven",
        "userlogin-signwithsecure": "Beveiligde verbinding gebruiken",
        "cannotloginnow-title": "Niet mogelijk om aan te melden",
index 10d005d..77ca16e 100644 (file)
        "yourpasswordagain": "Skriv opp att passordet",
        "createacct-yourpasswordagain": "Stadfest passord",
        "createacct-yourpasswordagain-ph": "Skriv inn passordet på nytt",
-       "remembermypassword": "Hugs innlogginga mi på denne datamaskinen (høgst {{PLURAL:$1|éin dag|$1 dagar}})",
        "userlogin-remembermypassword": "Hald meg innlogga",
        "userlogin-signwithsecure": "Nytt trygg kopling",
        "yourdomainname": "Domenet ditt",
        "minoredit": "Småplukk",
        "watchthis": "Overvak sida",
        "savearticle": "Lagra sida",
+       "savechanges": "Publiser endringane",
        "publishpage": "Publiser sida",
        "publishchanges": "Publiser endringar",
        "preview": "Førehandsvising",
index 2ccdece..4048ef8 100644 (file)
        "yourpasswordagain": "Confirmar lo senhal :",
        "createacct-yourpasswordagain": "Confirmatz lo senhal",
        "createacct-yourpasswordagain-ph": "Entratz lo senhal tornarmai",
-       "remembermypassword": "Me reconnectar automaticament a las visitas venentas (al maximum $1 {{PLURAL:$1|jorn|jorns}})",
        "userlogin-remembermypassword": "Gardar ma session activa",
        "userlogin-signwithsecure": "Utilizar una connexion securizada",
        "cannotloginnow-title": "Impossible de se connectar ara",
        "minoredit": "Aquò es un cambiament menor",
        "watchthis": "Seguir aquesta pagina",
        "savearticle": "Salvar",
+       "savechanges": "Enregistrar los cambiaments",
        "publishpage": "Publicar la pagina",
        "publishchanges": "Publicar las modificacions",
        "preview": "Previsualizar",
index 48f846a..e5f0c42 100644 (file)
        "yourpasswordagain": "Powtórz hasło:",
        "createacct-yourpasswordagain": "Potwierdź hasło",
        "createacct-yourpasswordagain-ph": "Wprowadź hasło jeszcze raz",
-       "remembermypassword": "Zapamiętaj moje logowanie na tym komputerze (maksymalnie przez $1 {{PLURAL:$1|dzień|dni}})",
        "userlogin-remembermypassword": "Nie wylogowuj mnie",
        "userlogin-signwithsecure": "Użyj bezpiecznego połączenia",
+       "cannotlogin-text": "Logowanie nie jest możliwe.",
        "cannotloginnow-title": "W tej chwili nie można się teraz zalogować",
        "cannotloginnow-text": "Podczas korzystania z $1 nie można się zalogować.",
        "yourdomainname": "Twoja domena:",
index f40ed82..bff6d79 100644 (file)
        "yourpasswordagain": "پټنوم بيا وليکه",
        "createacct-yourpasswordagain": "پټنوم مو تاييد کړۍ",
        "createacct-yourpasswordagain-ph": "پټنوم مو بيا وټاپئ",
-       "remembermypassword": "زما پټنوم په دې کمپيوټر (تر $1 {{PLURAL:$1|ورځې|ورځو}}) په ياد وساته!",
        "userlogin-remembermypassword": "غونډال کې مې ننوتلی وساته",
        "userlogin-signwithsecure": "خوندي اړيکتيا کارول",
        "yourdomainname": "ستاسې شپول:",
        "passwordreset-emailtitle": "د {{SITENAME}} د گڼون څرگندنې",
        "passwordreset-emailelement": "کارن-نوم: \n$1\n\nلنډمهاله پټنوم: \n$2",
        "passwordreset-emailsentemail": "د پټنوم بيا پرځای کېدنې لپاره برېښليک درولېږل شو.",
-       "passwordreset-emailsent-capture": "د پټنوم بياپرځای کېدنې لپار مو يو برېښليک درولېږه، برېښليک په لاندې توگه ښودل شوی.",
        "passwordreset-invalideamil": "ناسمه برېښليک پته",
        "changeemail": "برېښليک پته بدلول يا ليرې کول",
        "changeemail-header": "د گڼون برېښليک پته بدلول",
        "post-expand-template-argument-warning": "'''گواښنه:''' دا مخ لږ تر لږه د يوې کينډۍ عاملين لري چې بې حده لوی دی.\nدا عاملين ړنگ شول.",
        "post-expand-template-argument-category": "هغه مخونه چې د کينډۍ ړنگ شوي عاملين لري.",
        "undo-norev": "دا سمون ناکړل کېدای نه شي دا ځکه چې دا سمون نشته او يا هم ړنگ شوی.",
-       "cantcreateaccounttitle": "گڼون نه شي جوړېدای",
        "viewpagelogs": "د دې مخ يادښتونه کتل",
        "nohistory": "ددې مخ د سمون کوم پېښليک نه شته.",
        "currentrev": "اوسنۍ بڼه",
        "pageinfo-article-id": "د مخ پېژند",
        "pageinfo-language": "د مخ د مېنځپانگې ژبه",
        "pageinfo-content-model": "د مخ مېنځپانگې جوړښت",
+       "pageinfo-content-model-change": "بدلول",
        "pageinfo-robot-policy": "ليکلړ اوډنه د روباټونو لخوا",
        "pageinfo-robot-index": "پرېښل",
        "pageinfo-robot-noindex": "ناپرېښل",
index a4514e7..ae189ec 100644 (file)
@@ -98,7 +98,8 @@
                        "Luan",
                        "Anderson Costa",
                        "LucyDiniz",
-                       "Tusca"
+                       "Tusca",
+                       "Cristofer Alves"
                ]
        },
        "tog-underline": "Sublinhar links:",
        "yourpasswordagain": "Redigite sua senha",
        "createacct-yourpasswordagain": "Confirmar senha",
        "createacct-yourpasswordagain-ph": "Digite a senha novamente",
-       "remembermypassword": "Lembrar meu login neste navegador (por no máximo $1 {{PLURAL:$1|dia|dias}})",
        "userlogin-remembermypassword": "Mantenha-me conectado",
        "userlogin-signwithsecure": "Use a conexão segura",
        "cannotloginnow-title": "Não é possível iniciar a sessão agora",
        "userlogin-resetpassword-link": "Esqueceu sua senha?",
        "userlogin-helplink2": "Ajuda com o login",
        "userlogin-loggedin": "Você já está conectado como {{GENDER:$1|$1}}.\nUse o formulário abaixo para iniciar sessão como outro usuário.",
+       "userlogin-reauth": "Deve iniciar novamente a sessão para verificar se é {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Crie uma outra conta",
        "createacct-emailrequired": "Endereço de e-mail",
        "createacct-emailoptional": "Endereço de e-mail (opcional)",
        "createacct-email-ph": "Confirme seu endereço de e-mail",
        "createacct-another-email-ph": "Forneça o endereço de e-mail",
        "createaccountmail": "Usar uma senha aleatória e temporária que será enviada ao endereço de e-mail especificado a seguir",
+       "createaccountmail-help": "Pode ser utilizado para criar uma conta para outra pessoa sem saber a senha.",
        "createacct-realname": "Nome real (opcional)",
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Por que você está criando outra conta",
+       "createacct-reason-help": "Mensagem mostrada no registro de criação de conta",
        "createacct-submit": "Crie sua conta",
        "createacct-another-submit": "Criar conta",
+       "createacct-continue-submit": "Continuar criação de conta",
+       "createacct-another-continue-submit": "Continuar criação de conta",
        "createacct-benefit-heading": "{{SITENAME}} é feita por pessoas como você.",
        "createacct-benefit-body1": "{{PLURAL:$1|edição|edições}}",
        "createacct-benefit-body2": "{{PLURAL:$1|página|páginas}}",
        "nocookiesnew": "A conta do usuário foi criada, mas você não foi autenticado.\n{{SITENAME}} utiliza ''cookies'' para autenticar os usuários.\nVocê tem os ''cookies'' desativados no seu navegador.\nPor favor ative-os, depois autentique-se com o seu novo nome de usuário e a sua senha.",
        "nocookieslogin": "Você tem os <i>cookies</i> desativados no seu navegador, e a {{SITENAME}} utiliza <i>cookies</i> para ligar os usuários às suas contas. Por favor os ative e tente novamente.",
        "nocookiesfornew": "A conta de usuário não foi criada porque não foi possível confirmar a sua origem.\nCertifique-se de que tem os cookies ativados, recarregue esta página e tente novamente.",
+       "createacct-loginerror": "A conta foi criada com êxito, mas não pôde ser autenticado automaticamente. Por favor, faça o [[Special:UserLogin|início de sessão manualmente]].",
        "noname": "Você não colocou um nome de usuário válido.",
        "loginsuccesstitle": "Autenticado",
        "loginsuccess": "'''Agora você está {{GENDER:autenticado|autenticada}} ao wiki {{SITENAME}} como \"$1\"'''.",
        "botpasswords-label-delete": "Apagar",
        "botpasswords-label-resetpassword": "Redefinir a sua senha",
        "botpasswords-label-grants": "Permissões aplicáveis",
+       "botpasswords-help-grants": "Cada permissão da acesso à lista permissões de usuários que um usuário já tenha. Veja o [[Special:ListGrants|Lista de Permissões]] para mais informações.",
        "botpasswords-label-restrictions": "Restrições de uso:",
        "botpasswords-label-grants-column": "Concedido",
        "botpasswords-bad-appid": "O nome de robô \"$1\" não é válido.",
        "passwordreset-emailsentemail": "Se este é um endereço de e-mail registrado para a sua conta, em seguida, um e-mail de redefinição de senha será enviada.",
        "passwordreset-emailsentusername": "Se houver um endereço de email associado a esta conta, ser-lhe-á enviada uma mensagem para redefinir a sua senha.",
        "passwordreset-emailsent-capture2": "A redefinição da senha {{PLURAL:$1|do e-mail|dos e-mails}} foi enviada. {{PLURAL:$1|O nome de usuário e senha|A lista de nomes de usuário e senhas}} encontram-se a seguir.",
+       "passwordreset-emailerror-capture2": "O envio do e-mail {{GENDER:$2|usuário}} falhou: $1 Os {{PLURAL:$3|nome de usuário e senha|lista de nomes de usuários e senhas}} são mostrados abaixo.",
+       "passwordreset-nocaller": "Um interlocutor deve ser fornecido",
+       "passwordreset-nosuchcaller": "O interlocutor não existe: $1",
+       "passwordreset-ignored": "A redefinição da senha não foi realizada. Talvez o provedor não tenha sido configurado?",
+       "passwordreset-invalideamil": "Endereço de e-mail inválido",
+       "passwordreset-nodata": "Não foram fornecidos nome de usuário nem endereço de e-mail",
        "changeemail": "Alterar ou remover endereço de email",
        "changeemail-header": "Preencha este formulário para alterar seu endereço de e-mail. Se você gostaria de remover a associação de qualquer endereço de e-mail da sua conta, deixe o novo endereço de email em branco quando enviar o formulário.",
        "changeemail-no-info": "Para acessar diretamente esta página você tem de estar autenticado.",
        "minoredit": "Marcar como edição menor",
        "watchthis": "Vigiar esta página",
        "savearticle": "Salvar página",
+       "savechanges": "Salvar alterações",
        "publishpage": "Publicar página",
        "publishchanges": "Publicar alterações",
        "preview": "Pré-visualização",
        "accmailtext": "Uma senha gerada aleatoriamente para [[User talk:$1|$1]] foi enviada para $2.\n\nEla pode ser alterada na página ''[[Special:ChangePassword|de troca de senha]]'', após o início de sessão.",
        "newarticle": "(Nova)",
        "newarticletext": "Você seguiu um link para uma página que ainda não existe.\nPara criá-la, comece escrevendo na caixa abaixo (veja [$1 a página de ajuda] para mais informações).\nSe você chegou aqui por engano, clique no botão '''voltar''' do seu navegador.",
-       "anontalkpagetext": "---- ''Esta é a página de discussão para um usuário anônimo que ainda não criou uma conta ou que não a usa, de forma que temos de utilizar o endereço de IP para identificá-lo(a). Tal endereço de IP pode ser compartilhado por vários usuários. Se você é um usuário anônimo e acha que comentários irrelevantes foram direcionados a você, por gentileza, [[Special:CreateAccount|crie uma conta]] ou [[Special:UserLogin|autentique-se]], a fim de evitar futuras confusões com outros usuários anônimos.''",
+       "anontalkpagetext": "---\n<em> Esta é a pagina de discussão para usuários anônimos que ainda não ciaram uma conta, ou para aqueles que a usa. </em>\nNós entretanto temos que usar o endereço numérico de IP para identifica-lo/a.\nEste endereço de IP pode ser compartilhado por vários usuários.\nse você é um usuário anônimo e sente que aquele comentário irrelevante foi direcionado à você, por favor [[Special:CreateAccount|Criar Conta]] ou [[Special:UserLogin|Logar]] para evitar futuras confusões com outros usuários anônimos.",
        "noarticletext": "Não há conteúdo nesta página no momento.\nVocê pode [[Special:Search/{{PAGENAME}}|pesquisar pelo título desta página]] em outras páginas, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar por registros relacionados],\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} criar esta página]</span>.",
        "noarticletext-nopermission": "No momento, não há conteúdo nesta página\nVocê pode [[Special:Search/{{PAGENAME}}|pesquisar pelo título desta página]] em outras páginas,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar por registros relacionados] </span>. Note que, no entanto, você não tem permissão para criar esta página.",
        "missing-revision": "A revisão #$1 da página denominada \"{{FULLPAGENAME}}\" não existe.\n\nIsto é geralmente causado por seguir um link de histórico desatualizado para uma página que foi eliminada.\nOs detalhes podem ser encontrados no [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de eliminação].",
        "userpage-userdoesnotexist": "A conta \"<nowiki>$1</nowiki>\" não se encontra registrada.\nVerifique se deseja mesmo criar/editar esta página.",
        "userpage-userdoesnotexist-view": "A conta de usuário \"$1\" não está registrada.",
        "blocked-notice-logextract": "Este usuário está atualmente bloqueado.\nO registro de bloqueio mais recente é fornecido abaixo, para referência:",
-       "clearyourcache": "Nota:''' Depois de salvar, você terá de limpar o ''cache'' do seu navegador para ver as alterações.\n* '''Firefox / Safari:''' pressione ''Shift'' enquanto clica em ''Recarregar'', ou pressione ''Ctrl-F5'' ou ''Ctrl-R'' (''Command-R'' para Mac);\n* '''Google Chrome:''' pressione ''Ctrl-Shift-R'' (''Command-Shift-R'' em um Mac)\n* '''Internet Explorer:''' pressione ''Ctrl'' enquanto clica em ''Recarregar'' ou pressione ''Ctrl-F5'';\n* '''Opera:''' limpe o ''cache'' em ''Ferramentas → Preferências'' (''Tools → Preferences'')",
+       "clearyourcache": "<strong>Nota:</strong> Após salvar, você pode ter que limpar o \"cache\" do seu navegador para ver as alterações.\n*<strong>Firefox / Safari:</strong> Pressione <em>Shift</em> enquanto clica <em>Recarregar</em>, ou pressione <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> no Mac)\n*<strong>Google Chorme:</strong> Pressione <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> no Mac)\n* <strong>Internet Explorer:</strong> Pressione<em>Ctrl</em> enquanto clica <em>Recarregar</em>, ou Pressione <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Vá para <em>Menu → Configurações</em> (<em>Opera → Preferencias</em> no Mac) e depois para <em>Privacidade e Segurança → Limpar dados de navegação → Imagens e arquivos em cache</em>.",
        "usercssyoucanpreview": "'''Dica:''' Utilize o botão \"{{int:showpreview}}\" para testar seu novo CSS antes de salvar.",
        "userjsyoucanpreview": "'''Dica:''' Utilize o botão \"{{int:showpreview}}\" para testar seu novo JavaScript antes de salvar.",
        "usercsspreview": "'''Lembre-se de que você está apenas previsualizando o seu CSS particular.'''\n'''Ele ainda não foi salvo!'''",
        "content-json-empty-object": "Objeto vazio",
        "content-json-empty-array": "Array vazia",
        "deprecated-self-close-category": "Páginas com etiquetas HTML de autofechamento não válidas",
+       "deprecated-self-close-category-desc": "A página contém tags HTML auto-fechadas inválidas, como <code>&lt;b/></code> ou <code>&lt;span/></code>. O comportamento destas mudará em breve para coincidam com as especificações do HTML5, pelo que seu uso no wikitext está obsoleto.",
        "duplicate-args-warning": "<strong> Aviso: </strong> [[:$1]] está chamando [[:$2]] com mais de um valor para o parâmetro \"$3\". Será utilizado apenas o último valor fornecido.",
        "duplicate-args-category": "Páginas com argumentos de predefinições duplicados",
        "duplicate-args-category-desc": "A pagina contem modelos que usam argumentos duplicados, como <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> ou <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "grant-group-high-volume": "Realizar grande volume de atividades",
        "grant-group-customization": "Personalização e preferências",
        "grant-group-administration": "Realizar ações administrativas",
+       "grant-group-private-information": "acessar os dados privados sobre você",
        "grant-group-other": "Atividade diversa",
        "grant-blockusers": "Bloquear e desbloquear usuários",
        "grant-createaccount": "Criar contas",
        "grant-highvolume": "Edição de grandes volumes",
        "grant-oversight": "Ocultar usuários e revisões suprimidas",
        "grant-patrol": "Patrulhar as alterações nas páginas",
+       "grant-privateinfo": "acessar informações privadas",
        "grant-protect": "Proteger e desproteger páginas",
        "grant-rollback": "Reverter alterações nas páginas",
        "grant-sendemail": "Enviar e-mail a outros usuários",
        "rightslogtext": "Este é um registro de mudanças nos privilégios de usuários.",
        "action-read": "ler esta página",
        "action-edit": "editar esta página",
-       "action-createpage": "criar páginas",
-       "action-createtalk": "criar páginas de discussão",
+       "action-createpage": "criar esta páginas",
+       "action-createtalk": "criar esta páginas de discussão",
        "action-createaccount": "criar esta conta de usuário",
        "action-autocreateaccount": "Criar uma conta de usuário externa automaticamente",
        "action-history": "Ver o histórico desta página",
        "action-applychangetags": "aplicar etiquetas juntamente com suas alterações",
        "action-changetags": "adicionar e remover etiquetas arbitrárias em revisões e ''logs'' individuais",
        "action-deletechangetags": "deletar marcações da base de dados",
+       "action-purge": "purgar esta página",
        "nchanges": "$1 {{PLURAL:$1|alteração|alterações}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|desde a última visita}}",
        "enhancedrc-history": "histórico",
        "uploaded-event-handler-on-svg": "Não é permitido configurar atributos que manipulem eventos  <code>$1=\"$2\"</code> em arquivos SVG.",
        "uploaded-href-attribute-svg": "os atributos href nos ficheiros SVG só están autorizados a ligar a direccións http:// ou https://, atopado <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-href-unsafe-target-svg": "Encontrado href para dados não seguros: alvo URI <code>&lt;$1 $2=\"$3\"&gt;</code> no arquivo SVG carregado.",
+       "uploaded-animate-svg": "Encontrado a tag \"animate\" que pode estar mudando \"href\", usando o atributo \"from\" <code>&lt;$1 $2=\"$3\"&gt;</code> no arquivo SVG carregado.",
        "uploadscriptednamespace": "Este aruivo SVG contém um espaço nominal probido \"$1\"",
        "uploadinvalidxml": "O XML no arquivo enviado não pôde ser analisado.",
        "uploadvirus": "O arquivo contém vírus!\nDetalhes: $1",
        "trackingcategories-msg": "Categoria de monitoramento",
        "trackingcategories-name": "Nome da mensagem",
        "trackingcategories-desc": "Critérios de inclusão de categoria",
+       "restricted-displaytitle-ignored": "Páginas com títulos de exibição ignorados",
+       "restricted-displaytitle-ignored-desc": "Esta página tem um <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> ignorado porque não é equivalente ao título verdadeiro da página.",
        "noindex-category-desc": "A página não é indexada por robôs, porque possui a palavra mágica <code><nowiki>__NOINDEX__</nowiki></code> e está em um namespace onde a flag é permitida.",
        "index-category-desc": "A página contém a palavra mágica <code><nowiki>__INDEX__</nowiki></code> (e está num domínio em que essa marca é permitida) e, portanto, será indexada pelos robôs mesmo quando normalmente não o seria.",
        "post-expand-template-inclusion-category-desc": "O tamanho da página é superior a <code>$wgMaxArticleSize</code>, após a expansão de todas as predefinições, pelo que algumas predefinições não foram expandidas.",
index 2989d3b..21334a4 100644 (file)
        "yourpasswordagain": "Repita a palavra-passe:",
        "createacct-yourpasswordagain": "Confirme a palavra-passe",
        "createacct-yourpasswordagain-ph": "Digite a palavra-passe novamente",
-       "remembermypassword": "Recordar os meus dados neste computador (no máximo, por $1 {{PLURAL:$1|dia|dias}})",
        "userlogin-remembermypassword": "Manter-me autenticado",
        "userlogin-signwithsecure": "Usar uma ligação segura",
        "cannotloginnow-title": "Não é possível iniciar sessão agora",
        "botpasswords-deleted-body": "O robô palavra-passe para o nome do robô \"$1\"do utilizador \"$2\" foi eliminado.",
        "botpasswords-newpassword": "A nova palavra-passe para iniciar sessão com <strong>$1</strong> é <strong>$2</strong>. Por favor, recorde-se dela para futura referência.</em>",
        "botpasswords-no-provider": "BotPasswordsSessionProvider não está disponível.",
+       "botpasswords-restriction-failed": "Restrições de senha de robô evitam esta autenticação.",
+       "botpasswords-invalid-name": "O nome de usuário especificado não contém o separador de senha de robô (\"$1\").",
+       "botpasswords-not-exist": "O usuário \"$1\" não possui uma senha de robô \"$2\".",
        "resetpass_forbidden": "Não é possível alterar palavras-passe",
        "resetpass_forbidden-reason": "As palavras-passe não podem ser alteradas: $1",
        "resetpass-no-info": "Precisa de iniciar sessão para aceder diretamente a esta página.",
        "passwordreset-emailelement": "{{GENDER:$1|Utilizador|Utilizadora}}: \n$1\n\nPalavra-passe temporária: \n$2",
        "passwordreset-emailsentemail": "Se este é o endereço de correio eletrónico associado a esta conta, ser-lhe-á enviada uma palavra-passe de reposição.",
        "passwordreset-emailsentusername": "Se houver um endereço de correio eletrónico associado a esta conta, ser-lhe-á enviada uma mensagem para redefinir a sua palavra-passe.",
+       "passwordreset-emailsent-capture2": "A redefinição da senha {{PLURAL:$1|do e-mail|dos e-mails}} foi enviada. {{PLURAL:$1|O nome de usuário e senha|A lista de nomes de usuário e senhas}} encontram-se a seguir.",
+       "passwordreset-emailerror-capture2": "O envio do correio {{GENDER:$2|ao usuário|à usuária}} falhou: $1 {{PLURAL:$3|O nome de usuário e senha são mostradas abaixo|A lista de nomes de usuários e senhas é mostrada abaixo}}.",
+       "passwordreset-nocaller": "Um interlocutor deve ser fornecido",
+       "passwordreset-nosuchcaller": "A pessoa que chama não existe: $1",
+       "passwordreset-ignored": "A redefinição de senha não foi realizada. Talvez o provedor não tenha sido configurado, sim?",
        "passwordreset-invalideamil": "Correio eletrónico inválido",
        "passwordreset-nodata": "Não foram fornecidos nome de utilizador(a) nem endereço de correio eletrónico",
        "changeemail": "Alterar ou remover o endereço de correio eletrónico",
        "apisandbox-loading-results": "A receber resultados da API...",
        "apisandbox-request-url-label": "URL do pedido:",
        "apisandbox-request-time": "Tempo de processamento: {{PLURAL:$1|$1 ms}}",
+       "apisandbox-results-fixtoken": "Corrija o identificador e envie-o novamente",
+       "apisandbox-results-fixtoken-fail": "Não foi possível recuperar o identificador \"$1\".",
+       "apisandbox-alert-page": "Os campos nesta página não são válidos.",
        "apisandbox-alert-field": "O valor deste campo não é válido.",
        "booksources": "Fontes bibliográficas",
        "booksources-search-legend": "Pesquisar referências bibliográficas",
        "trackingcategories-msg": "Categoria monitorada",
        "trackingcategories-name": "Nome da mensagem",
        "trackingcategories-desc": "Critérios de inclusão",
+       "restricted-displaytitle-ignored": "Páginas com títulos de exibição ignorados",
+       "restricted-displaytitle-ignored-desc": "Esta página tem um <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> ignorado porque não é equivalente ao título verdadeiro da página.",
        "noindex-category-desc": "A página não é indexada por robôs porque contém a palavra mágica <code><nowiki>__NOINDEX__</nowiki></code> e está num domínio onde o estatuto é permitido.",
        "index-category-desc": "A página contém a palavra mágica <code><nowiki>__INDEX__</nowiki></code> (e está num domínio em que essa marca é permitida) e, portanto, será indexada pelos robôs mesmo quando normalmente não o seria.",
        "post-expand-template-inclusion-category-desc": "O tamanho da página é superior a <code>$wgMaxArticleSize</code>, após a expansão de todas as predefinições, pelo que algumas predefinições não foram expandidas.",
        "rollbacklinkcount-morethan": "reverter mais do que $1 {{PLURAL:$1|edição|edições}}",
        "rollbackfailed": "A reversão falhou",
        "rollback-missingparam": "Faltam parâmetros obrigatórios no pedido.",
+       "rollback-missingrevision": "Não é possível carregar os dados de revisão.",
        "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}}]]);\nalguém editou ou já reverteu a página.\n\nA última edição foi de [[User:$3|$3]] ([[User talk:$3|discussão]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "O resumo da edição era: <em$1</em>.",
        "special-characters-group-ipa": "AFI (IPA)",
        "special-characters-group-symbols": "Símbolos",
        "special-characters-group-greek": "Grego",
+       "special-characters-group-greekextended": "Grego estendido",
        "special-characters-group-cyrillic": "Cirílico",
        "special-characters-group-arabic": "Árabe",
        "special-characters-group-arabicextended": "Arábico estendido",
        "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
        "mw-widgets-titleinput-description-new-page": "a página ainda não existe.",
        "mw-widgets-titleinput-description-redirect": "redirecionar para $1",
+       "sessionmanager-tie": "Não se pode combinar múltiplas solicitações de tipos de autenticação: $1.",
        "sessionprovider-generic": "Sessões $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sessões baseadas em cookie",
        "sessionprovider-nocookies": "Os cookies podem estar desativados. Certifique-se de que os cookies estão ativados e inicie novamente.",
        "log-action-filter-suppress-block": "Supressão de utilizadores por bloqueio",
        "log-action-filter-upload-upload": "Novo carregamento",
        "log-action-filter-upload-overwrite": "Recarregar",
+       "authmanager-authn-no-primary": "As informações de identificação fornecidas não podem ser autenticadas.",
+       "authmanager-authn-autocreate-failed": "A criação automática de uma conta local falhou: $1",
        "authmanager-create-disabled": "A criação de contas está desativada.",
        "authmanager-create-from-login": "Para criar a sua conta, por favor, preencha os campos abaixo.",
        "authmanager-authplugin-setpass-failed-title": "A alteração de palavra-passe falhou",
        "authpage-cannot-login-continue": "Não é possível continuar a iniciar sessão. A sua sessão pode ter expirado.",
        "authpage-cannot-create": "Não é possível iniciar a criação da conta.",
        "authpage-cannot-create-continue": "Não é possível continuar a criação da conta. A sua sessão pode ter expirado.",
+       "authpage-cannot-link": "Não se pode iniciar a vinculação da conta.",
+       "authpage-cannot-link-continue": "Não é possível continuar a criação da conta. A sua sessão pode ter expirado.",
        "cannotauth-not-allowed-title": "Permissão negada",
        "cannotauth-not-allowed": "Não possui permissão para utilizar esta página",
        "changecredentials": "Alterar credenciais",
index 48f2013..924d25f 100644 (file)
                        "2axterix2",
                        "Ата",
                        "Matěj Suchánek",
-                       "Chaduvari"
+                       "Chaduvari",
+                       "MarcoAurelio"
                ]
        },
        "sidebar": "{{notranslate}}",
        "yourpasswordagain": "Since 1.22 no longer used in core, but may be used by some extensions. DEPRECATED",
        "createacct-yourpasswordagain": "In create account form, label for field to re-enter password\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n{{Identical|Confirm password}}",
        "createacct-yourpasswordagain-ph": "Placeholder text in create account form for re-enter password field.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
-       "remembermypassword": "Used as checkbox label on [[Special:ChangePassword]]. Parameters:\n* $1 - number of days\n{{Identical|Remember my login on this computer}}",
        "userlogin-remembermypassword": "The text for a check box in [[Special:UserLogin]].",
        "userlogin-signwithsecure": "Text of link to HTTPS login form.\n\nSee example: [[Special:UserLogin]]",
-       "cannotloginnow-title": "Error page title shown when logging in is not possible.",
-       "cannotloginnow-text": "Error page text shown when logging in is not possible. Parameters:\n* $1 - Session type in use that makes it not possible to log in, from a message like {{msg-mw|sessionprovider-mediawiki-session-cookiesessionprovider}}.",
+       "cannotlogin-title": "Error page title shown when logging in is not possible. This is a catch-all when a more specific reason is not available.",
+       "cannotlogin-text": "Error page text shown when logging in is not possible. This is a catch-all when a more specific reason is not available.",
+       "cannotloginnow-title": "Error page title shown when logging in is not possible becuse the session provider in use does the user authentication itself.",
+       "cannotloginnow-text": "Error page text shown when logging in is not possible becuse the session provider in use does the user authentication itself. Parameters:\n* $1 - Session type in use that makes it not possible to log in, from a message like {{msg-mw|sessionprovider-mediawiki-session-cookiesessionprovider}}.",
+       "cannotcreateaccount-title": "Error page title shown when manual account creation is not possible. That probably means the wiki supports some other account creation method, e.g. autocreation by the session provider. (When account creation is possible but the current user does not have permission to do it, a more specific error message is displayed.)",
+       "cannotcreateaccount-text": "Error page text shown when manual account creation is not possible. That probably means the wiki supports some other account creation method, e.g. autocreation by the session provider. (When account creation is possible but the current user does not have permission to do it, a more specific error message is displayed.)",
        "yourdomainname": "Used as label for listbox.",
        "password-change-forbidden": "Error message shown when an external authentication source does not allow the password to be changed.",
        "externaldberror": "This message is thrown when a valid attempt to change the wiki password for a user fails because of a database error or an error from an external system.",
        "file-thumbnail-no": "Error message at [[Special:Upload]]. Parameters:\n* $1 - String (e.g. \"180px-\")",
        "fileexists-forbidden": "{{doc-important|''thumb'' and ''center'' are magic words. Leave it untranslated!}}\nParameters:\n* $1 - name of the existing file",
        "fileexists-shared-forbidden": "{{doc-important|''thumb'' and ''center'' are magic words. Leave it untranslated!}}\nError message at [[Special:Upload]].\nParameters:\n* $1 - name of the existing file",
+       "fileexists-no-change": "Used in [[Special:Upload]] to warn when the upload is an exact duplicate of the current version of the file.\nParameters:\n* $1 - name of the existing file",
+       "fileexists-duplicate-version": "Used in [[Special:Upload]] to warn when the upload is an exact duplicate of (an) older version(s) of the file\nParameters:\n* $1 - name of the existing file\n* $2 - Amount of matching older versions (PLURAL support)",
        "file-exists-duplicate": "Used as warning in [[Special:Upload]].\nThis message is followed by the gallery of the duplicate files.\n\nParameters:\n* $1 - number of duplicate files",
        "file-deleted-duplicate": "Used in [[Special:Upload]. Parameters:\n* $1 - page title of the file\n\nSee also:\n* {{msg-mw|file-deleted-duplicate-notitle}}",
        "file-deleted-duplicate-notitle": "Used in [[Special:Upload]] when the title of the deleted duplicate is not available.\n\nSee also:\n* {{msg-mw|file-deleted-duplicate}}",
        "filerevert-submit": "{{Identical|Revert}}",
        "filerevert-success": "Message displayed when you succeed in reverting a version of a file.\n* $1 is the name of the media\n* $2 is a date\n* $3 is a time\n* $4 is an URL and must follow square bracket: [$4\n{{Identical|Revert}}",
        "filerevert-badversion": "Used as error message.",
+       "filerevert-identical": "Used as error message.",
        "filedelete": "Used as page title. Parameters:\n* $1 - file title\nSee also:\n* {{msg-mw|Filedelete-intro}}",
        "filedelete-legend": "Used as fieldset label in the \"Delete file\" form.\n{{Identical|Delete file}}",
        "filedelete-intro": "Used as introduction for FileDelete form. Parameters:\n* $1 - page title for file\nSee also:\n* {{msg-mw|Filedelete|page title}}",
        "pageinfo-article-id": "The numeric identifier of the page.\n{{Identical|Page ID}}",
        "pageinfo-language": "Language in which the page content is written.",
        "pageinfo-content-model": "The model in which the page content is written.\n\nUsed as label at [{{fullurl:Main Page|action=info}} action=info]. Followed by one of the following messages:\n* {{msg-mw|Content-model-wikitext}}\n* {{msg-mw|Content-model-javascript}}\n* {{msg-mw|Content-model-css}}\n* {{msg-mw|Content-model-text}}",
+       "pageinfo-content-model-change": "Link text for a link to Special:ChangeContentModel. The link will be wrapped in parenthesis.\n{{Identical|Change}}",
        "pageinfo-robot-policy": "The search engine status of the page.\n\nUsed as label. Followed by any one of the following messages:\n*{{msg-mw|Pageinfo-robot-index}}\n*{{msg-mw|Pageinfo-robot-noindex}}",
        "pageinfo-robot-index": "An indication that the page is indexable by search engines, that is listed in their search results.\n\nPreceded by the label {{msg-mw|Pageinfo-robot-policy}}.\n{{Identical|Allowed}}",
        "pageinfo-robot-noindex": "An indication that the page is not indexable (that is, is not listed on the results page of a search engine).\n\nPreceded by the label {{msg-mw|Pageinfo-robot-policy}}.",
        "logentry-delete-revision": "{{Logentry|[[Special:Log/delete]]}}\n{{Logentryparam}}\n* $5 - the number of affected revisions of the page $3",
        "logentry-delete-event-legacy": "{{Logentry|[[Special:Log/delete]]}}",
        "logentry-delete-revision-legacy": "{{Logentry|[[Special:Log/delete]]}}",
-       "logentry-suppress-delete": "{{Logentry}}\n\n'Hid' is a possible alternative to 'suppressed' in this message.",
+       "logentry-suppress-delete": "{{Logentry}}\n\n'Hid' is a possible alternative to 'suppressed' in this message.\n\n'''This is not a normal deletion log entry''' and is used only when the page is removed even from administrators view (on WMF wikis this is called 'oversight' or 'suppression'.",
        "logentry-suppress-event": "{{Logentry}}\n{{Logentryparam}}\n* $5 - count of affected log events",
        "logentry-suppress-revision": "{{Logentry}}\n{{Logentryparam}}\n* $5 - the number of affected revisions of the page $3.",
        "logentry-suppress-event-legacy": "{{Logentry}}",
index de84e8e..bb89a09 100644 (file)
        "yourpasswordagain": "Yaykuna rimaykita kutipayay",
        "createacct-yourpasswordagain": "Yaykuna rimata takyachiy",
        "createacct-yourpasswordagain-ph": "Yaykuna rimata musuqmanta yaykuchiy",
-       "remembermypassword": "Ruraqpa sutiyta yaykuna rimaytapas yuyaykuy llamk'ay tiyayniypura ({{PLURAL:$1|huk p'unchawkama|$1 p'unchawkama}})",
        "userlogin-remembermypassword": "Yaykusqa kakunaytam munani",
        "userlogin-signwithsecure": "Amachasqa t'inkinakusqata llamk'achiy",
        "yourdomainname": "Duminyuykip sutin",
        "passwordreset-emailtext-user": "{{SITENAME}}-pi kaq $1 sutiyuq ruraqqa {{SITENAME}}-paq ($4)\nrakiqunaykipaq yaykuna rimata kutichinatam mañakurqan. Kay qatiq ruraqpa {{PLURAL:$3|rakiqunanmi|rakiqunankunam}}\nkay e-chaski imamaytayuq kachkan:\n\n$2\n\nKay mit'alla yaykuna {{PLURAL:$3|rimaqa|rimakunaqa}} kunanmanta {{PLURAL:$5|huk p'unchawpi|$5 p'unchawpi}} mawk'ayanqam.\nYaykuspayki musuq yaykuna rimaykitam akllankiman. Pi wakiykipas kayta mañakurqaptinqa,\nicha qam ñawpaq yaykuna rimaykita yuyaspayki manaña wakinchayta munaspaykiqa,\nkay willayta mana qhawaspa mana imatapas ruraspa ñawpaq yaykuna rimaykiwanmi llamk'ayta atinki.",
        "passwordreset-emailelement": "Ruraqpa sutin: \n$1\n\nMit'alla yaykuna rima: \n$2",
        "passwordreset-emailsentemail": "Yaykuna rimata kutichina e-chaskiqa kachasqañam.",
-       "passwordreset-emailsent-capture": "Yaykuna rimata kutichina e-chaskiqa kachasqañam, kay qatiqpi rikunki.",
-       "passwordreset-emailerror-capture": "{{GENDER:$2|}}Yaykuna rimata kutichina e-chaskiqa rurasqa karqan, imatachus kay qatiqpi rikunki, ichataq kachasqa kaptin pantasqam tukurqan: $1",
        "changeemail": "E-chaski imamaytata wakinchay",
        "changeemail-header": "Rakiqunap e-chaski imamaytanta wakinchay",
        "changeemail-no-info": "Yaykunaykim tiyan kay p'anqata chiqalla aypanaykipaq.",
        "minoredit": "Kayqa uchuylla hukchaymi",
        "watchthis": "Kay qillqata watiqay",
        "savearticle": "P'anqata waqaychay",
+       "savechanges": "Hukchasqata waqaychay",
        "preview": "Manaraq waqaychaspa qhawariy",
        "showpreview": "Ñawpaqta qhawallay",
        "showdiff": "Hukchasqakunata rikuchiy",
        "undo-norev": "Manam atinichu llamk'apusqata kutichiyta, mana kaptinmi icha qullusqa kaptinmi.",
        "undo-summary": "[[Special:Contributions/$2|$2]]-pa $1 hukchasqanta kutichisqa ([[User talk:$2|rimay]])",
        "undo-summary-username-hidden": "Pakasqa ruraqpa $1 nisqa musuqchasqata kutichiy",
-       "cantcreateaccounttitle": "Manam atinichu rakiqunata kichayta",
        "cantcreateaccount-text": "Kay IP tiyaymanta ('''$1''') rakiquna kichariyqa [[User:$3|$3]]-pa hark'asqanmi.\n\n$3-qa nirqan kayraykum: ''$2''",
        "viewpagelogs": "Kay p'anqamanta hallch'akunata qhaway",
        "nohistory": "Kay p'anqamantaqa manam llamk'apuy wiñay kawsay kanchu.",
index 2dafc36..0dba318 100644 (file)
        "yourpasswordagain": "Repetați parola:",
        "createacct-yourpasswordagain": "Confirmare parolă",
        "createacct-yourpasswordagain-ph": "Introduceți parola din nou",
-       "remembermypassword": "Autentificare automată de la acest calculator (expiră după {{PLURAL:$1|24 de ore|$1 zile|$1 de zile}})",
        "userlogin-remembermypassword": "Păstrează-mă autentificat",
        "userlogin-signwithsecure": "Utilizează conexiunea securizată",
        "cannotloginnow-title": "Nu se poate conecta acum",
        "minoredit": "Aceasta este o modificare minoră",
        "watchthis": "Urmărește această pagină",
        "savearticle": "Salvare pagină",
+       "savechanges": "Salvează modificările",
        "publishpage": "Publică pagina",
        "publishchanges": "Publică modificările",
        "preview": "Previzualizare",
index cd4e304..0dade8b 100644 (file)
        "yourpasswordagain": "Повторный набор пароля:",
        "createacct-yourpasswordagain": "Подтвердите пароль",
        "createacct-yourpasswordagain-ph": "Введите пароль еще раз",
-       "remembermypassword": "Помнить мою учётную запись на этом компьютере (не более $1 {{PLURAL:$1|дня|дней}})",
        "userlogin-remembermypassword": "Оставаться в системе",
        "userlogin-signwithsecure": "Защищённое соединение",
+       "cannotlogin-title": "Невозможно войти",
+       "cannotlogin-text": "Вход в систему невозможен.",
        "cannotloginnow-title": "Невозможно войти прямо сейчас",
        "cannotloginnow-text": "Нельзя войти во время использования $1.",
+       "cannotcreateaccount-title": "Невозможно создать учётные записи",
+       "cannotcreateaccount-text": "Прямое создание учетных записей не включено в этой вики.",
        "yourdomainname": "Ваш домен:",
        "password-change-forbidden": "Вы не можете изменить пароль в этой вики.",
        "externaldberror": "Произошла ошибка при аутентификации с помощью внешней базы данных или у вас недостаточно прав для внесения изменений в свою внешнюю учётную запись.",
        "file-thumbnail-no": "Название файла начинается с <strong>$1</strong>.\nВероятно, это уменьшенная копия изображения ''(миниатюра)''.\nЕсли у вас есть данное изображение в полном размере, пожалуйста, загрузите его или измените имя файла.",
        "fileexists-forbidden": "Файл с этим именем уже существует и не может быть перезаписан.\nЕсли всё равно хотите загрузить данный файл, пожалуйста, вернитесь назад и загрузите его под другим именем. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Файл с этим именем уже существует в общем хранилище файлов.\nЕсли вы всё-таки хотите загрузить этот файл, пожалуйста, вернитесь назад и измените имя файла. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Эта загрузка является точной копией текущей версии файла <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Эта загрузка является точной копией {{PLURAL:$2|более старой версии|более старых версий}} файла <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Этот файл — дубликат {{PLURAL:$1|1=следующего файла|следующих файлов}}:",
        "file-deleted-duplicate": "Подобный файл ([[:$1]]) уже удалялся. Пожалуйста, ознакомьтесь с историей удаления файла, прежде чем загружать его снова.",
        "file-deleted-duplicate-notitle": "Файл, идентичный этому файлу, был ранее удалён, а имя файла было запрещено.\nВам следует попросить кого-нибудь с правами просмотра данных по запрещённым файлам, чтобы он проанализировал ситуацию перед тем, как загружать файл снова.",
        "zip-wrong-format": "Указанный файл — не ZIP-архив.",
        "zip-bad": "ZIP-файл повреждён или не может быть прочитан.\nОн не может быть должным образом проверен.",
        "zip-unsupported": "Этот ZIP-файл использует возможности, не поддерживаемые MediaWiki.\nОн не может быть должным образом проверен.",
-       "uploadstash": "СкÑ\80Ñ\8bÑ\82наÑ\8f Ð·Ð°Ð³Ñ\80Ñ\83зка",
+       "uploadstash": "Ð\97агÑ\80Ñ\83зка Ð²Ð¾ Ð²Ñ\80еменное Ñ\85Ñ\80анилиÑ\89е",
        "uploadstash-summary": "Данная страница предоставляет доступ к файлам, которые были загружены (или находятся в процессе загрузки), но ещё не были опубликованы в вики. Эти файлы никому не видны, кроме загрузившего их участника.",
-       "uploadstash-clear": "Очистить скрытые файлы",
-       "uploadstash-nofiles": "У вас нет скрытых файлов.",
+       "uploadstash-clear": "Очистить временные файлы",
+       "uploadstash-nofiles": "У вас нет временных файлов.",
        "uploadstash-badtoken": "Не удалось выполнить указанные действия. Возможно, истёк срок действия ваших учётных данных. Пожалуйста, пробуйте ещё раз.",
        "uploadstash-errclear": "Очистка файлов не удалась.",
        "uploadstash-refresh": "Обновить список файлов",
        "uploadstash-thumbnail": "показать миниатюру",
+       "uploadstash-exception": "Не удалось сохранить загрузку во временное хранилище ($1): «$2».",
        "invalid-chunk-offset": "Недопустимое смещение фрагмента",
        "img-auth-accessdenied": "Доступ запрещён",
        "img-auth-nopathinfo": "Отсутствует <code>PATH_INFO</code>.\nВаш сервер не настроен для передачи этих сведений.\nВозможно, он работает на основе CGI и не поддерживает <code>img_auth</code>.\nСм. https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "filerevert-submit": "Возвратить",
        "filerevert-success": "'''[[Media:$1|$1]]''' был возвращён к [$4 версии от $3, $2].",
        "filerevert-badversion": "Не существует предыдущей локальной версии этого файла с указанной меткой времени.",
+       "filerevert-identical": "Текущая версия файла уже идентична выбранной.",
        "filedelete": "$1 — удаление",
        "filedelete-legend": "Удалить файл",
        "filedelete-intro": "Вы собираетесь удалить файл '''[[Media:$1|$1]]''' со всей его историей.",
        "pageinfo-article-id": "Идентификатор страницы",
        "pageinfo-language": "Язык страницы",
        "pageinfo-content-model": "Модель содержимого страницы",
+       "pageinfo-content-model-change": "изменить",
        "pageinfo-robot-policy": "Индексация поисковыми роботами",
        "pageinfo-robot-index": "Разрешено",
        "pageinfo-robot-noindex": "Не разрешено",
        "api-error-ratelimited": "Вы пытаетесь загрузить несколько файлов за более короткий промежуток времени, чем это позволено.\nПожалуйста, попробуйте ещё раз через несколько минут.",
        "api-error-stashfailed": "Внутренняя ошибка: сервер не смог сохранить временный файл.",
        "api-error-publishfailed": "Внутренняя ошибка: сервер не смог сохранить временный файл.",
-       "api-error-stasherror": "При загрузке файла в хранилище произошла ошибка.",
+       "api-error-stasherror": "При загрузке файла во временное хранилище произошла ошибка.",
        "api-error-stashedfilenotfound": "При попытке загрузить файл из временного хранилища исходный файл не найден.",
        "api-error-stashpathinvalid": "Путь, по которому должен располагаться файл, загруженный во временное хранилище, некорректен.",
        "api-error-stashfilestorage": "При загрузке файла во временное хранилище произошла ошибка.",
index 238e705..8f664fc 100644 (file)
        "yourpasswordagain": "कूटशब्दः पुनः लिख्यताम् :",
        "createacct-yourpasswordagain": "कूटशब्दस्य पुष्टिं करोतु ।",
        "createacct-yourpasswordagain-ph": "कूटशब्दः पुनः लिख्यताम्",
-       "remembermypassword": "अस्मिन् सङ्गणके मम प्रवेशः स्मर्यताम् (अधिकतमम् $1 {{PLURAL:$1|दिनम्|दिनानि}})",
        "userlogin-remembermypassword": "अहं प्रविष्ट एव स्याम्",
        "userlogin-signwithsecure": "संरक्षितः सम्पर्कः (https) उपयुज्यताम्",
        "yourdomainname": "भवतः प्रदेशः (domain) :",
        "passwordreset-capture-help": "अस्यां मञ्जूषायां यदि भवता अङ्क्यते तर्हि वि-पत्रम् (अस्थायिकूटशब्देन सह) दर्श्यते प्रेष्यते च ।",
        "passwordreset-email": "वि-पत्रसङ्केतः",
        "passwordreset-emailtitle": "{{SITENAME}} इत्यत्र योजकविषये",
-       "passwordreset-emailtext-ip": "कोऽपि (कदाचित् भवान्/भवती, $1 अन्तर्जालसंविदः (from IP)) {{SITENAME}}($4) जालस्थानस्य  कृते कूटशब्दपरिवर्तनस्य विनतिम् अकरोत् । निम्न{{PLURAL:$3|योजकः|योजकाः}} अनेन वि-पत्रेण सह सल्लग्नः अस्ति/सल्लग्नाः सन्ति ।\n\n$2\n\n{{PLURAL:$3|एषः अल्पकालीनकूटशब्दः|एते अल्पकालीनकूटशब्दाः}} {{PLURAL:$5|चतुर्विंशतिघण्टासु|$5 दिनेषु}} निरस्तः भविष्यति/निरस्ताः भविष्यन्ति ।\nअधुना प्रवेशं सम्प्राप्य कूटशब्दः परिवर्तनीयः एव । \n\nनिम्नकारणानि यदि सन्ति, तर्हि एनं सन्देशम् अवगण्यताम् ।\n\n१ कोऽपि अन्यः अत्र विनतिम् अकरोत् । \n२ पूरातनः कूटशब्दः भवतः/भवत्याः स्मरणे अस्ति ।\n३ भवान्/भवती कूटशब्दं परिवर्तयितुं नेच्छिति ।",
-       "passwordreset-emailtext-user": "$1 सदस्यः {{SITENAME}}($4) जालस्थानस्य  कृते कूटशब्दपरिवर्तनस्य विनतिम् अकरोत् । निम्न{{PLURAL:$3|सदस्यः|सदस्याः}} अनेन वि-पत्रेण सह सल्लग्नः अस्ति/सल्लग्नाः सन्ति ।\n\n$2\n\n{{PLURAL:$3|एषः अल्पकालीनकूटशब्दः|एते अल्पकालीनकूटशब्दाः}} {{PLURAL:$5|चतुर्विंशतिघण्टासु|$5 दिनेषु}} निरस्तः भविष्यति/निरस्ताः भविष्यन्ति ।\nअधुना प्रवेशं सम्प्राप्य कूटशब्दः परिवर्तनीयः एव । \n\nनिम्नकारणानि यदि सन्ति, तर्हि एनं सन्देशम् अवगण्यताम् ।\n\n१ कोऽपि अन्यः अत्र विज्ञप्तिम् अकरोत् । \n२ पूरातनः कूटशब्दः भवतः/भवत्याः स्मरणे अस्ति ।\n३ भवान्/भवती कूटशब्दं परिवर्तयितुं नेच्छिति ।",
+       "passwordreset-emailtext-ip": "कोऽपि (कदाचित् भवान्/भवती, $1 अन्तर्जालसंविदः (from IP)) {{SITENAME}}($4) जालस्थानस्य कृते कूटशब्दपरिवर्तनस्य विनतिम् अकरोत् । अनेन वि-पत्रेण सह निम्न{{PLURAL:$3|योजकः सल्लग्नः अस्ति|योजकाः सल्लग्नाः सन्ति}} ।\n\n$2\n\n{{PLURAL:$5|चतुर्विंशतिघण्टासु|$5 दिनेषु}} {{PLURAL:$3|एषः अल्पकालीनकूटशब्दः निरस्तः भविष्यति|एते अल्पकालीनकूटशब्दाः निरस्ताः भविष्यन्ति}} ।\n\nअधुना प्रवेशं सम्प्राप्य कूटशब्दः परिवर्तनीयः एव । \n\nनिम्नकारणानि यदि सन्ति, तर्हि एनं सन्देशम् अवगण्यताम् ।\n\n१ कोऽपि अन्यः अत्र विनतिम् अकरोत् । \n२ पुरातनः कूटशब्दः भवतः/भवत्याः स्मरणे अस्ति ।\n३ भवान्/भवती कूटशब्दं परिवर्तयितुं नेच्छति ।",
+       "passwordreset-emailtext-user": "कोऽपि (कदाचित् भवान्/भवती, $1 अन्तर्जालसंविदः (from IP)) {{SITENAME}}($4) जालस्थानस्य कृते कूटशब्दपरिवर्तनस्य विनतिम् अकरोत् । अनेन वि-पत्रेण सह निम्न{{PLURAL:$3|योजकः सल्लग्नः अस्ति|योजकाः सल्लग्नाः सन्ति}} ।\n\n$2\n\n{{PLURAL:$5|चतुर्विंशतिघण्टासु|$5 दिनेषु}} {{PLURAL:$3|एषः अल्पकालीनकूटशब्दः निरस्तः भविष्यति|एते अल्पकालीनकूटशब्दाः निरस्ताः भविष्यन्ति}} ।\n\nअधुना प्रवेशं सम्प्राप्य कूटशब्दः परिवर्तनीयः एव । \n\nनिम्नकारणानि यदि सन्ति, तर्हि एनं सन्देशम् अवगण्यताम् ।\n\n१ कोऽपि अन्यः अत्र विनतिम् अकरोत् । \n२ पुरातनः कूटशब्दः भवतः/भवत्याः स्मरणे अस्ति ।\n३ भवान्/भवती कूटशब्दं परिवर्तयितुं नेच्छति ।",
        "passwordreset-emailelement": "सदस्यनाम : \n$1\n\nअल्पकालीनकूटशब्दः : \n$2",
        "passwordreset-emailsentemail": "परिवर्तितकूटशब्दस्य वि-पत्रं प्रेषितम् अस्ति ।",
        "changeemail": "वि-पत्रसङ्केतः परिवर्त्यताम्",
index efe836e..3480e4f 100644 (file)
        "yourpasswordagain": "Zopakujte heslo:",
        "createacct-yourpasswordagain": "Potvrdiť heslo",
        "createacct-yourpasswordagain-ph": "Zadajte heslo znova",
-       "remembermypassword": "Pamätať si prihlásenie na tomto počítači (naviac $1 {{PLURAL:$1|deň|dni|dní}})",
        "userlogin-remembermypassword": "Zapamätať si ma",
        "userlogin-signwithsecure": "Použiť zabezpečené pripojenie",
        "yourdomainname": "Vaša doména:",
index f9cf164..88c6886 100644 (file)
        "yourpasswordagain": "Ponovno vpišite geslo",
        "createacct-yourpasswordagain": "Potrdite geslo",
        "createacct-yourpasswordagain-ph": "Ponovno vnesite geslo",
-       "remembermypassword": "Zapomni si me na tem računalniku (za največ $1 {{PLURAL:$1|dan|dneva|dni}})",
        "userlogin-remembermypassword": "Zapomni si me",
        "userlogin-signwithsecure": "Uporabi varno povezavo",
+       "cannotlogin-title": "Ne moremo vas prijaviti",
+       "cannotlogin-text": "Prijava ni mogoča.",
        "cannotloginnow-title": "Trenutno se ne morete prijaviti",
        "cannotloginnow-text": "Prijava ni možna pri uporabi $1.",
+       "cannotcreateaccount-title": "Ne moremo ustvariti računov",
+       "cannotcreateaccount-text": "Neposredno ustvarjanje računov na tem wikiju ni omogočeno.",
        "yourdomainname": "Domena",
        "password-change-forbidden": "Na tem wikiju ne morete spreminjati gesel.",
        "externaldberror": "Pri potrjevanju istovetnosti je prišlo do notranje napake ali pa za osveževanje zunanjega računa nimate dovoljenja.",
        "file-thumbnail-no": "Ime datoteke se začne z <strong>$1</strong>.\nIzgleda, da je to pomanjšana slika ''(thumbnail)''.\nČe imate sliko polne resolucije, jo naložite, drugače spremenite ime datoteke.",
        "fileexists-forbidden": "Datoteka s tem imenom že obstaja in je ni mogoče prepisati.\nČe še vedno želite naložiti vašo datoteko, se prosimo vrnite nazaj in uporabite novo ime.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Datoteka s tem imenom že obstaja v skupnem skladišču datotek.\nProsimo, vrnite se in naložite svojo datoteko pod drugim imenom.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Naložena datoteka je točen dvojnik trenutne različice <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Naložena datoteka je točen dvojnik {{PLURAL:$2|starejše različice|starejših različic}} <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Ta datoteka je dvojnik {{PLURAL:$1|naslednje datoteke|naslednjih datotek}}:",
        "file-deleted-duplicate": "Datoteka je identična tej ([[:$1]]), ki je bila predhodno izbrisana.\nPreverite zgodovino brisanja datoteke, preden jo ponovno naložite.",
        "file-deleted-duplicate-notitle": "Datoteka, identična tej datoteki, je bila v preteklosti izbrisana in naslov je bil zatrt.\nPoprosite koga, ki ima možnost ogleda podatkov zatrtih datotek, da preveri položaj, preden nadaljujete s ponovnim nalaganjem.",
        "filerevert-submit": "Vrni",
        "filerevert-success": "Datoteka '''[[Media:$1|$1]]''' je bila vrnjena na [$4 različico $3, $2].",
        "filerevert-badversion": "Ne najdem preteklih lokalnih verzij datoteke s podanim časovnim žigom.",
+       "filerevert-identical": "Trenutna različica datoteke je že enaka izbrani.",
        "filedelete": "Izbriši $1",
        "filedelete-legend": "Brisanje datoteke",
        "filedelete-intro": "Brišete datoteko '''[[Media:$1|$1]]''' skupaj z njeno celotno zgodovino.",
        "pageinfo-article-id": "ID strani",
        "pageinfo-language": "Jezik vsebine strani",
        "pageinfo-content-model": "Model vsebine strani",
+       "pageinfo-content-model-change": "spremeni",
        "pageinfo-robot-policy": "Robotsko indeksiranje",
        "pageinfo-robot-index": "Dovoljeno",
        "pageinfo-robot-noindex": "Nedovoljeno",
index 26b3ffd..b04c775 100644 (file)
        "yourpasswordagain": "Потврда лозинке:",
        "createacct-yourpasswordagain": "Потврдите лозинку",
        "createacct-yourpasswordagain-ph": "Унесите лозинку још једном",
-       "remembermypassword": "Запамти ме на овом прегледачу (најдуже $1 {{PLURAL:$1|дан|дана}})",
        "userlogin-remembermypassword": "Остави ме пријављеног/у",
        "userlogin-signwithsecure": "Користите сигурну конекцију",
        "yourdomainname": "Домен:",
        "file-thumbnail-no": "Датотека почиње са <strong>$1</strong>.\nИзгледа да се ради о умањеној слици ''(thumbnail)''.\nУколико имате ову слику у пуној величини, пошаљите је, а ако немате, промените назив датотеке.",
        "fileexists-forbidden": "Датотека с овим називом већ постоји и не може се заменити.\nАко и даље желите да пошаљете датотеку, вратите се и изаберите други назив.\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Датотека с овим називом већ постоји у заједничкој остави.\nВратите се и пошаљите датотеку с другим називом.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Датотека је дупликат тренутне верзије <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Датотека је дупликат {{PLURAL:$2|старе верзије|старих верзија}} <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Ово је дупликат {{PLURAL:$1|следеће датотеке|следећих датотека}}:",
        "file-deleted-duplicate": "Датотека истоветна овој ([[:$1]]) је претходно обрисана.\nПогледајте историју брисања пре поновног слања.",
        "file-deleted-duplicate-notitle": "Датотека идентична овој претходно је обрисана и име јој је сакривено.\nТребали бисте питати некога ко може видети податке скривених датотека да прегледа ситуацију пре него што поново отпремите датотеку.",
        "filerevert-submit": "Врати",
        "filerevert-success": "Датотека '''[[Media:$1|$1]]''' је враћена на [$4 издање од $2; $3].",
        "filerevert-badversion": "Не постоји раније локално издање датотеке с наведеним временским подацима.",
+       "filerevert-identical": "Тренутна верзија датотеке индентична је изабраној.",
        "filedelete": "Обриши $1",
        "filedelete-legend": "Обриши датотеку",
        "filedelete-intro": "Бришете датотеку '''[[Media:$1|$1]]''' заједно с њеном историјом.",
index 492912d..5331c26 100644 (file)
@@ -73,7 +73,8 @@
                        "Matma Rex",
                        "McDutchie",
                        "Larske",
-                       "Rockyfelle"
+                       "Rockyfelle",
+                       "Johan"
                ]
        },
        "tog-underline": "Stryk under länkar:",
        "yourpasswordagain": "Upprepa lösenord",
        "createacct-yourpasswordagain": "Bekräfta lösenordet",
        "createacct-yourpasswordagain-ph": "Ange lösenordet igen",
-       "remembermypassword": "Spara min inloggning på den här datorn (i max $1 {{PLURAL:$1|dygn}})",
        "userlogin-remembermypassword": "Håll mig inloggad",
        "userlogin-signwithsecure": "Använd säker anslutning",
        "cannotloginnow-title": "Kan inte logga in nu",
        "file-thumbnail-no": "Filnamnet börjar med <strong>$1</strong>.\nDet verkar vara en bild med förminskad storlek ''(miniatyrbild)''.\nOm du har denna bild i full storlek, ladda då hellre upp den, annars var vänlig och ändra filens namn.",
        "fileexists-forbidden": "En fil med detta namn existerar redan, och kan inte skrivas över.\nOm du ändå vill ladda upp din fil, gå då tillbaka och använd ett annat namn. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "En fil med detta namn finns redan bland de delade filerna.\nOm du ändå vill ladda upp din fil, gå då tillbaka och använd ett annat namn. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Den uppladdade filen är en exakt kopia av den aktuella versionen av <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Den uppladdade versionen är en exakt kopia av {{PLURAL:$2|en äldre version|äldre versioner}} av <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Denna fil är en dubblett av följande {{PLURAL:$1|fil|filer}}:",
        "file-deleted-duplicate": "En identisk fil till den här filen ([[:$1]]) har tidigare raderats. \nDu bör kontrollera den filens raderingshistorik innan du fortsätter att ladda upp den på nytt.",
        "file-deleted-duplicate-notitle": "En identisk fil till den här filen har tidigare raderats och titeln har undanhållits.\nDu borde be någon som kan se undanhållen fildata att granska situationen innan du försöker ladda upp den på nytt.",
        "filerevert-submit": "Återställ",
        "filerevert-success": "'''[[Media:$1|$1]]''' har återställts till [$4 versionen från $2 kl. $3].",
        "filerevert-badversion": "Det finns ingen tidigare version av filen från den angivna tidpunkten.",
+       "filerevert-identical": "Den aktuella versionen av filen är redan identisk med den valda.",
        "filedelete": "Radera $1",
        "filedelete-legend": "Radera fil",
        "filedelete-intro": "Du håller på att radera filen '''[[Media:$1|$1]]''' tillsammans med hela dess historik.",
        "pageinfo-article-id": "Sid-ID",
        "pageinfo-language": "Språk för sidinnehåll",
        "pageinfo-content-model": "Sidinnehållsmodell",
+       "pageinfo-content-model-change": "ändra",
        "pageinfo-robot-policy": "Indexering av robotar",
        "pageinfo-robot-index": "Tillåten",
        "pageinfo-robot-noindex": "Inte tillåten",
index ee6bff0..3fa93b8 100644 (file)
                        "Lokesha kunchadka"
                ]
        },
-       "tog-underline": "ಲಿà²\82à²\95à³\8dâ\80\99ಲà³\86ದ à²¤à²¿à²°à³\8dತà³\8d à²\97à³\86ರà³\86(à²\85à²\82ಡರà³\8d à²²à³\88ನà³\8d) à²ªà²¾à²¡à³\8dâ\80\99ಲೆ",
-       "tog-hideminor": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ à²¬à²¦à²²à²¾à²µà²£ೆಲೆನ್ ದೆಂಗಾಲೆ",
-       "tog-hidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ à²¸à²\82ಪದನà³\86ಲà³\86ನà³\8d à²\87à²\82à²\9aಿಪà³\8aದ à²¬à²¦à²²à²¾à²µà²£à³\86ಡà³\8d à²\85ಡà³\86à²\82à²\97ಲ",
-       "tog-newpageshidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ à²ªà³\81à²\9fà³\8aಲà³\86ನà³\8d à²ªà³\8aಸ à²ªà³\81à²\9fà³\8aà²\95à³\81ಲà³\86 à²ªà²\9fà³\8dà²\9fಿಡà³\8d à²\85ಡà³\86à²\82à²\97ಲ",
-       "tog-hidecategorization": "ವಿà²\82à²\97ಡಿತà³\8dâ\80\8dನ à²ªà³\81à²\9fà³\8aಲà³\86ನà³\8d à²\85ಡà³\86à²\82à²\97ಲ",
-       "tog-extendwatchlist": "à²\95à³\87ವಲà³\8a à²\87à²\82à²\9aಿಪà³\8aದ à²¬à²¦à²²à²¾à²µà²£à³\86ಲತà³\8dತà²\82ದà³\86, à²¸à²\82ಬà²\82ದà³\8a à²\87ಪà³\8dಪà³\81ನ à²®à²¾à²¤ à²¬à²¦à²²à²¾à²µà²£ೆನ್ಲಾ ತೋಜುನಂಚನೆ ಪಟ್ಟಿನ್ ವಿಸ್ತರಿಸಲೆ",
-       "tog-usenewrc": "à²\87à²\82à²\9aಿಪà³\8aದ à²¬à²¦à²²à²¾à²µà²£à³\86 à²¬à³\8aà²\95à³\8dà²\95à³\8a à²µà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\97à³\81à²\82ಪà³\81 à²ªà³\81à²\9fà³\8a à²¬à²¦à²²à²¾à²µà²£ೆ",
-       "tog-numberheadings": "ಹà³\86ಡà³\8dಡಿà²\82à²\97à³\8dâ\80\99ಲà³\86à²\97à³\8d à²¸à²\82à²\96à³\8dಯà³\86ಲà³\86ನà³\8d à²¤à³\8aà²\9cà³\8dಪಾಲà³\86",
-       "tog-showtoolbar": "ಸà²\82ಪಾದನà³\86ದ à²\89ಪà²\95ರಣೊ ಪಟ್ಟಿನ್ ತೋಜಾವು",
+       "tog-underline": "ಲಿà²\82à²\95à³\8dâ\80\8dಲà³\86ದ à²¤à²¿à²°à³\8dತà³\8d à²\97à³\86ರà³\86(à²\85à²\82ಡರà³\8d à²²à³\88ನà³\8d) à²ªà²¾à²¡à³\8dâ\80\8dಲೆ",
+       "tog-hideminor": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ à²¬à²¦à²²à²¾à²µà²¨ೆಲೆನ್ ದೆಂಗಾಲೆ",
+       "tog-hidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ à²¸à²\82ಪದನà³\86ಲà³\86ನà³\8d à²\87à²\82à²\9aಿಪà³\8aದ à²¬à²¦à²²à²¾à²µà²¨à³\86ಡà³\8d à²¦à³\86à²\82à²\97ಾಲ",
+       "tog-newpageshidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ à²ªà³\81à²\9fà³\8aಲà³\86ನà³\8d à²ªà³\8aಸ à²ªà³\81à²\9fà³\8aà²\95à³\81ಲà³\86 à²ªà²\9fà³\8dà²\9fಿಡà³\8d à²¦à³\86à²\82à²\97ಾಲ",
+       "tog-hidecategorization": "ವಿà²\82à²\97ಡಿತà³\8dâ\80\8dನ à²ªà³\81à²\9fà³\8aಲà³\86ನà³\8d à²¦à³\86à²\82à²\97ಾಲ",
+       "tog-extendwatchlist": "à²\95à³\87ವಲà³\8a à²\87à²\82à²\9aಿಪà³\8aದ à²¬à²¦à²²à²¾à²µà²¨à³\86ಲತà³\8dತà²\82ದà³\86, à²¸à²\82ಬà²\82ದà³\8a à²\87ಪà³\8dಪà³\81ನ à²®à²¾à²¤ à²¬à²¦à²²à²¾à²µà²¨ೆನ್ಲಾ ತೋಜುನಂಚನೆ ಪಟ್ಟಿನ್ ವಿಸ್ತರಿಸಲೆ",
+       "tog-usenewrc": "à²\87à²\82à²\9aಿಪà³\8aದ à²¬à²¦à²²à²¾à²µà²¨à³\86 à²¬à³\8aà²\95à³\8dà²\95à³\8a à²µà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\97à³\81à²\82ಪà³\81 à²ªà³\81à²\9fà³\8a à²¬à²¦à²²à²¾à²µà²¨ೆ",
+       "tog-numberheadings": "ತರà³\86ಬರವà³\81ಲà³\86à²\97à³\8d à²\85à²\82à²\95à³\86ಲà³\86ನà³\8d à²¤à³\8bà²\9cಾವà³\81",
+       "tog-showtoolbar": "ಸà²\82ಪಾದನà³\86ದ à²\89ಪà²\95ರನೊ ಪಟ್ಟಿನ್ ತೋಜಾವು",
        "tog-editondblclick": "ರಡ್ಡ್ ಸರ್ತಿ ಒತ್ತ್‌ನಗ ಪುಟೊನು ಸಂಪೊಲಿಪುನಂಚ ಆವಡ್",
        "tog-editsectiononrightclick": "ಪುಟೊತ ವಿಬಾಗೊಲೆನ್ ಐತ ಸೀರ್ಸಿಕೆನ್ ರಡ್ಡ್ ಸರ್ತಿ ಒತ್ತ್‌ನಗ ಸಂಪೊಲಿಪುನಂಚ ಉಪ್ಪಡ್",
-       "tog-watchcreations": "ಯಾನà³\8d à²ುರು ಮಲ್ತಿನ ಲೇಕನೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
+       "tog-watchcreations": "ಯಾನà³\8d à²¸ುರು ಮಲ್ತಿನ ಲೇಕನೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
        "tog-watchdefault": "ಯಾನ್ ಸಂಪೊಲಿಪುನ ಪುಟೊಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
-       "tog-watchmoves": "ಯಾನ್ ಸ್ತಲಾಂತರಿಸುನ ಪುಟೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
+       "tog-watchmoves": "ಯಾನà³\8d à²¸à³\8dತಲಾà²\82ತರಿಸಪà³\81ನ à²ªà³\81à²\9fà³\8aಲà³\86ನà³\8d à²\8eನà³\8dನ à²µà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿà²\97à³\8d à²¸à³\87ರà³\8dಪಾಲà³\86",
        "tog-watchdeletion": "ಯಾನ್ ದೆತ್ತ್‌ ಪಾಡುನ ಪುಟೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
-       "tog-watchuploads": "ಎನ್ನ ಅಪ್ಲೋಡ್ ಪಟ್ಟಿಗ್  ಪೊಸ ಕಡತೊಲೆನ್ ಸೇರಲ",
+       "tog-watchuploads": "ಎನ್ನ ಅಪ್ಲೋಡ್ ಪಟ್ಟಿಗ್ ಪೊಸ ಕಡತೊಲೆನ್ ಸೇರಲ",
        "tog-watchrollback": "ಯಾನ್ ಪಿರ ದೆತೊನುನ ಪುಟೊಲೆನ್ ಎನ್ನ ಗುಮನೊಗು ಸೇರಲೆ",
-       "tog-minordefault": "ಪà³\82ರಾ à²¸à²\82ಪಾದನà³\86ನà³\8dಲಾ à²\8eಲà³\8dಯ à²ªà²\82ಡà³\8dâ\80\99ದ್ ಗುರ್ತ ಮಲ್ಪುಲೆ",
-       "tog-previewontop": "ಮà³\81ನà³\8dನà³\8bà²\9fನà³\8d à²¸à²\82ಪಾದನà³\86 à²\85à²\82à²\95ಣದ ಮಿತ್ತ್ ತೊಜ್ಪಾಲೆ",
-       "tog-previewonfirst": "ಶà³\81ರà³\81ತ à²¬à²¦à²²à²¾à²µà²£ೆದ ಬೊಕ್ಕ ಮನ್ನೋಟನ್ ತೊಜ್ಪಾಲೆ",
+       "tog-minordefault": "ಪà³\82ರಾ à²¸à²\82ಪಾದನà³\86ನà³\8dಲಾ à²\8eಲà³\8dಯ à²ªà²\82ಡà³\8dâ\80\8dದ್ ಗುರ್ತ ಮಲ್ಪುಲೆ",
+       "tog-previewontop": "ಮà³\81ನà³\8dನà³\8bà²\9fನà³\8d à²¸à²\82ಪಾದನà³\86 à²\85à²\82à²\95ನà³\8aದ ಮಿತ್ತ್ ತೊಜ್ಪಾಲೆ",
+       "tog-previewonfirst": "ಸà³\81ತ à²¬à²¦à²²à²¾à²µà²¨ೆದ ಬೊಕ್ಕ ಮನ್ನೋಟನ್ ತೊಜ್ಪಾಲೆ",
        "tog-enotifwatchlistpages": "ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಡ್ ಉಪ್ಪುನಂಚಿನ ಒವಾಂಡಲ ಪುಟೊ ಬದಲಾನಗ ಎಂಕ್ ಇ-ಅಂಚೆ ಕಡಪುಡ್ಲೆ",
        "tog-enotifusertalkpages": "ಎನ್ನ ಚರ್ಚೆ ಪುಟ ಬದಲಾಂಡ ಎಂಕ್ ಇ-ಮೇಲ್ ಕಡಪುಡ್ಲೆ",
-       "tog-enotifminoredits": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ à²¬à²¦à²²à²¾à²µà²£ೆ ಆಂಡಲ ಎಂಕ್ ಇ-ಅಂಚೆ ಕಡಪುಡ್ಲೆ",
-       "tog-enotifrevealaddr": "ಪà³\8dರà²\95à²\9fಣà³\86 à²\87-ಮà³\87ಲà³\8dâ\80\99ಡ್ ಎನ್ನ ಇ-ಮೇಲ್ ವಿಳಾಸನ್ ತೊಜ್ಪಾಲೆ",
-       "tog-shownumberswatching": "ಪà³\81à²\9fà³\8aನà³\81 à²¤à³\82ವà³\8aà²\82ದà³\81ಪà³\8dಪà³\81ನà²\82à²\9aಿನ à²¸à²¦à²¸à³\8dಯà³\86ರà³\8dâ\80\99ನ ಸಂಖ್ಯೆನ್ ತೊಜ್ಪಾಲೆ",
+       "tog-enotifminoredits": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ à²¬à²¦à²²à²¾à²µà²¨ೆ ಆಂಡಲ ಎಂಕ್ ಇ-ಅಂಚೆ ಕಡಪುಡ್ಲೆ",
+       "tog-enotifrevealaddr": "ಪà³\8dರà²\95à²\9fಣà³\86 à²\87-ಮà³\87ಲà³\8dâ\80\8dಡ್ ಎನ್ನ ಇ-ಮೇಲ್ ವಿಳಾಸನ್ ತೊಜ್ಪಾಲೆ",
+       "tog-shownumberswatching": "ಪà³\81à²\9fà³\8aನà³\81 à²¤à³\82ವà³\8aà²\82ದà³\81ಪà³\8dಪà³\81ನà²\82à²\9aಿನ à²¸à²¦à²¸à³\8dಯà³\86ರà³\8dâ\80\8dನ ಸಂಖ್ಯೆನ್ ತೊಜ್ಪಾಲೆ",
        "tog-oldsig": "ಇತ್ತೆದ ಸಹಿ",
        "tog-fancysig": "ವಿಕಿಟೆಕ್ಸ್‌ಟ್‍ಗ್ ದಸ್ಕತ್ತ್‌ದ ಉಪಚಾರೊ(ಸ್ವಂತೊ ಚಾಲನೆದ ಕೊಂಡಿ ಇದ್ಯಂದಿಲೆಕ)",
        "tog-uselivepreview": "ನೇರೊ ಮುನ್ನೋಟೊನು ಉಪಯೋಗ ಮಲ್ಪುಲೆ",
-       "tog-forceeditsummary": "ಸà²\82ಪಾದನà³\86 à²¸à²¾à²°à²¾à²\82ಶà³\8aನà³\81 à²\96ಾಲಿ à²¬à³\81ಡà³\8dâ\80\99ನà³\8dಡ್ ಎಂಕ್ ನೆನಪು ಮಲ್ಪುಲೆ",
-       "tog-watchlisthideown": "ವà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\8eನà³\8dನ à²¸à²\82ಪಾದನà³\86ಲà³\86ನà³\8d à²¤à³\8aà²\9cà³\8dâ\80\99ಪಾವà³\8aಚಿ",
+       "tog-forceeditsummary": "ಸà²\82ಪಾದನà³\86 à²¸à²¾à²°à²¾à²\82ಸà³\8aನà³\81 à²\95ಾಲಿ à²¬à³\81ಡà³\8dâ\80\8dà²\82ದ್ ಎಂಕ್ ನೆನಪು ಮಲ್ಪುಲೆ",
+       "tog-watchlisthideown": "ವà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\8eನà³\8dನ à²¸à²\82ಪಾದನà³\86ಲà³\86ನà³\8d à²¤à³\8aà²\9cà³\8dâ\80\8dಪಾವà³\8aಡà³\8dಚಿ",
        "tog-watchlisthidebots": "ವೀಕ್ಷಣಾಪಟ್ಟಿಡ್ ಬಾಟ್ ಸಂಪಾದನೆಲೆನ್ ದೆಂಗಾಲೆ",
        "tog-watchlisthideminor": "ಎಲ್ಯ ಬದಲಾವಣೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
-       "tog-watchlisthideliu": "ಲಾà²\97ಿನà³\8d à²\86ತಿನà²\82à²\9aಿನ à²¸à²¦à²¸à³\8dಯà³\86ರà³\8dâ\80\99ನ ಸಂಪಾದನೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
+       "tog-watchlisthideliu": "ಲಾà²\97ಿನà³\8d à²\86ತಿನà²\82à²\9aಿನ à²¸à²¦à²¸à³\8dಯà³\86ರà³\8dâ\80\8dನ ಸಂಪಾದನೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
        "tog-watchlisthideanons": "ಪುದರಿಜ್ಜಂದಿನ ಬಳಕೆದಾರನ ಸಂಪಾದನೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
        "tog-watchlisthidepatrolled": "ವೀಕ್ಷಣಾಪಟ್ಟಿಡ್ ಬಾಟ್ ಸಂಪಾದನೆಲೆನ್ ದೆಂಗಾಲೆ",
        "tog-watchlisthidecategorization": "ವಿಂಗಡಿತ್‍ನ ಪುಟೊಲೆನ್ ಅಡೆಂಗಲ",
        "yourpasswordagain": "ಪಾಸ್ವರ್ಡ್ ಪಿರ ಟೈಪ್ ಮಲ್ಪುಲೆ",
        "createacct-yourpasswordagain": "ಪ್ರವೇಸೊ ಪದೊನು ದೃಡೊ ಮಲ್ಪುಲೆ",
        "createacct-yourpasswordagain-ph": "ಪ್ರವೇಸೊ ಪದೊನು ನನ ಒರ ನಮೂದಿಸಲೆ",
-       "remembermypassword": "ಈ ಗಣಕಯಂತ್ರೊಡು ಎನ್ನ ಲಾಗಿನ್ ನೆಂಪು ದೀಡೊನ್ಲೆ(ಹೆಚ್ಚ್ $1 {{PLURAL:$1|ದಿನೊತ|ದಿನೊಕ್ಕುಲೆ}}ಮುಟ್ಟೊ)",
        "userlogin-remembermypassword": "ಎನನ್ ಲಾಗಿನ್ ಆತೇ ದೀಡ್ಲೆ",
        "userlogin-signwithsecure": "ರಕ್ಷಣೆದ ಕನೆಕ್ಷನ್ ಉಪಯೋಗಿಸಲೆ.",
        "cannotloginnow-title": "ಇತ್ತೆ ಉಲಾಯಿ ಪೋಯರ್ ಸಾದ್ಯೊ ಇದ್ದಿ",
        "passwordreset-username": "ಸದಸ್ಯೆರ್ನ ಪುದರ್:",
        "passwordreset-domain": "ಕ್ಷೇತ್ರೊ:",
        "passwordreset-email": "ಇ-ಅಂಚೆ ವಿಳಾಸೊ",
+       "passwordreset-invalideamil": "ಇಮೇಲ್ ಸರಿ ಇಜ್ಜಿ",
+       "changeemail-oldemail": "ಇತ್ತೆತಾ ಈಮೇಲ್ ವಿಳಾಸೊ:",
        "changeemail-newemail": "ಪೊಸ ಇ-ಅಂಚೆ ವಿಳಾಸೊ:",
        "changeemail-none": "ಒವ್ವುಲಾ ಇಜ್ಜಿ",
        "changeemail-submit": "ಇ-ಅಂಚೆ ವಿಳಾಸ ಬದಲಾವಣೆ ಮಲ್ಪುಲೆ",
        "permissionserrors": "ಅನುಮತಿ ದೋಷ",
        "permissionserrorstext-withaction": "$2 ಗ್ ಇರೆಗ್ ಅನುಮತಿ ಇದ್ದಿ, ಅಯಿಕ್ {{PLURAL:$1|ಕಾರಣೊ|ಕಾರಣೊಲು}}:",
        "moveddeleted-notice": "ಈ ಪುಟೊ ಅಸ್ತಿತ್ವೊಡ್ ಇದ್ದಿ.\nಪುಟೊದ ಡಿಲೀಶನ್ ಅತ್ತ್ಂಡ್ ಕಡಪ್ಪುಡುನೆ ಲಾಗ್‍ನ್ ತೂಯರೆ ತಿರ್ತ್ ಕೊರ್ತ್ಂಡ್.",
+       "postedit-confirmation-created": "ಈ ಪುಟೋನು ಉಂಡು ಮಾನ್ತುಂಡು.",
        "postedit-confirmation-saved": "ಇರೇನಾ ಸಂಪಾದನೆನ್ ಒರಿಪಾತುಂಡು.",
        "edit-already-exists": "ಪೊಸ ಪುಟೋನು ಉಂಡು ಮಲ್ಪರೆ ಅಯಿಜಿ. ಅವ್ವು ದುಂಬೇ ಉಂಡು.",
        "content-model-wikitext": "ವಿಕಿ ಪಠ್ಯ",
        "revdelete-hide-image": "ಪೈಲ್‘ಡ್  ಇಪ್ಪುನ ಮಾಹಿತ್‘ನ್ ದೆಂಗಾಲೆ",
        "revdelete-hide-name": "ಕಾರ್ಯ ಬೊಕ್ಕ ಗುರಿನ್ ದೆಂಗಾಲ",
        "revdelete-hide-comment": "ಸಾರಾಂಶ ಸಂಪೊಲಿಪುಲೆ",
+       "revdelete-radio-same": "(ಬದಲಾವಣೆ ಮಾಂಪಾಡ್ಚಿ)",
        "revdelete-radio-set": "ದೆಂಗಾಲೆ",
        "revdelete-radio-unset": "ತೋಜುಂಡು",
        "revdelete-log": "ಕಾರಣ",
        "right-delete": "ಪುಟೊಕುಲೆನ್ ಮಾಜಾಲೆ",
        "right-undelete": "ಪುಟೊನ್ ಮಾಜಾವಡೆ",
        "grant-group-email": "ಇ-ಅಂಚೆ ಕಡಪುಡುಲೆ",
+       "grant-createaccount": "ಪೊಸ ಕಾತೆ ಸುರು ಮಲ್ಪುಲೆ",
        "newuserlogpage": "ಸದಸ್ಯೆರೆ ಸ್ರಿಸ್ಟಿದ ದಾಕಲೆ",
        "rightslog": "ಸದಸ್ಯೆರ್ನ ಹಕ್ಕು ದಾಖಲೆ",
        "action-read": "ಈ ಪುಟೊನು ಓದುಲೆ",
        "action-upload": "ಈ ಫೈಲ್‘ನ್ ಅಪ್‘ಲೋಡ್ ಮಲ್ಪುಲೆ",
        "action-delete": "ಈ ಪುಟೊನ್ ಮಾಜಾಲೆ",
        "action-deleterevision": "ಈ ಆವೃತ್ತಿನ್ ಮಾಜಾಲೆ",
+       "action-browsearchive": "ಮಜಾಯಿನಾ ಪುಟೋನ್ ನಡ್ಲೆ",
+       "action-undelete": "ಈ ಪುಟೊನ್ ಮಾಜಾಯಿನೆನ್ ರದ್ದ್ ಮಾನ್ಪುಲೇ",
        "action-sendemail": "ಇ-ಅಂಚೆ ಕಡಪುಡುಲೆ",
        "nchanges": "$1 {{PLURAL:$1|ಬದಲಾವಣೆ|ಬದಲಾವಣೆಲು}}",
        "enhancedrc-history": "ಇತಿಹಾಸೊ",
        "upload-disallowed-here": "ಈರ್ ಈ ಫೈಲ್‍ನ್ ಕುಡೊರೊ ಬರೆವರೆ ಸಾದ್ಯೊ ಇದ್ದಿ.",
        "filerevert-comment": "ಕಾರಣ:",
        "filerevert-submit": "ದುಂಬುದ ಲೆಕ ಮಲ್ಪುಲೆ",
+       "filedelete": "$1 ನ್ ಮಾಜಾಲೆ",
        "filedelete-legend": "ಕಡತನ್ ಮಾಜಾಲೆ",
        "filedelete-comment": "ಕಾರಣ",
        "filedelete-submit": "ಮಾಜಾಲೆ",
        "protectedpages": "ಸಂರಕ್ಷಿತ ಪುಟೊ",
        "protectedpages-page": "ಪುಟೊ",
        "protectedpages-reason": "ಕಾರಣೊ",
+       "protectedpages-submit": "ಪ್ರದರ್ಶಿಶಿಸಾಯಿನ ಪುದರ್",
        "protectedpages-unknown-timestamp": "ಗೊತ್ತಿಜ್ಜಾಂದಿನ",
        "protectedpages-unknown-performer": "ಅಜ್ಞಾತ ಬಳಕೆದಾರೆ",
        "protectedtitles": "ಸಂರಕ್ಷಿತ ಶೀರ್ಷಿಕೆಲು",
        "tooltip-summary": "ಒಂಜಿ ಎಲ್ಯ ಸಾರಾಂಸೊ ಕೊರ್ಲೆ",
        "simpleantispam-label": "ಯಾಂಟಿ-ಸ್ಪಾಮ್ ಚೆಕ್.\nಮುಲ್ಪ <strong>ದಿಂಜಾವೊಡ್ಚಿ</strong>",
        "pageinfo-article-id": "ಪುಟೊದ ಐಡಿ",
+       "pageinfo-content-model-change": "ಬದಲಾವಣೆಲು",
        "pageinfo-toolboxlink": "ಪುಟೊದ ಮಾಹಿತಿ",
        "pageinfo-contentpage-yes": "ಅಂದ್",
        "pageinfo-protect-cascading-yes": "ಅಂದ್",
index 4dad914..b49742e 100644 (file)
        "yourpasswordagain": "సంకేతపదాన్ని మళ్ళీ ఇవ్వండి:",
        "createacct-yourpasswordagain": "సంకేతపదాన్ని నిర్ధారించండి",
        "createacct-yourpasswordagain-ph": "సంకేతపదాన్ని మళ్ళీ ఇవ్వండి",
-       "remembermypassword": "ఈ కంప్యూటరులో నా ప్రవేశాన్ని గుర్తుంచుకో (గరిష్ఠంగా $1 {{PLURAL:$1|రోజు|రోజుల}}కి)",
        "userlogin-remembermypassword": "నన్ను లాగిన్ చేసే ఉంచు",
        "userlogin-signwithsecure": "సురక్షిత కనెక్షను వాడు",
        "cannotloginnow-title": "ఇప్పుడు లాగిన్ అవలేరు",
        "mergehistory-empty": "ఏ కూర్పులనూ విలీనం చెయ్యలేము.",
        "mergehistory-done": "$1 యొక్క $3 {{PLURAL:$3|కూర్పుని|కూర్పులను}} [[:$2]] లోనికి జయప్రదంగా విలీనం చేసాం.",
        "mergehistory-fail": "చరితాన్ని విలీనం చెయ్యలేకపోయాం. పేజీని, సమయాలను సరిచూసుకోండి.",
+       "mergehistory-fail-bad-timestamp": "కాలముద్ర చెల్లదు.",
        "mergehistory-no-source": "మూలం పేజీ, $1 లేదు.",
        "mergehistory-no-destination": "గమ్యం పేజీ, $1 లేదు.",
        "mergehistory-invalid-source": "మూలం పేజీకి సరైన పేరు ఉండాలి.",
        "grant-group-email": "ఈమెయిలు పంపించడం",
        "grant-group-administration": "నిర్వాహక చర్యలు చేపట్టడం",
        "grant-group-private-information": "మీ గోపనీయ డేటాను చూడడం",
+       "grant-basic": "ప్రాథమిక హక్కులు",
        "newuserlogpage": "కొత్త వాడుకరుల చిట్టా",
        "newuserlogpagetext": "ఇది వాడుకరి నమోదుల చిట్టా.",
        "rightslog": "వాడుకరుల హక్కుల మార్పుల చిట్టా",
        "apihelp-no-such-module": "\"$1\" మాడ్యూలు కనబడలేదు.",
        "apisandbox": "API ప్రయోగశాల",
        "apisandbox-api-disabled": "ఈ సైటులో API అచేతనమై ఉంది.",
+       "apisandbox-unfullscreen": "పేజీను చూపించు",
        "apisandbox-submit": "అభ్యర్ధించు",
        "apisandbox-reset": "తుడిచివేయి",
-       "apisandbox-examples": "ఉదాహరణ",
-       "apisandbox-results": "ఫలితం",
+       "apisandbox-retry": "మళ్ళీ ప్రయత్నించు",
+       "apisandbox-helpurls": "సహాయపు లంకెలు",
+       "apisandbox-examples": "ఉదాహరణలు",
+       "apisandbox-dynamic-parameters": "అదనపు పరామితులు",
+       "apisandbox-dynamic-parameters-add-label": "పరామితిని చేర్చు:",
+       "apisandbox-dynamic-parameters-add-placeholder": "పరామితి పేరు",
+       "apisandbox-dynamic-error-exists": "\"$1\" అనే పరామితి ఇప్పటికే ఉంది.",
+       "apisandbox-results": "ఫలితాలు",
        "apisandbox-request-url-label": "అభ్యర్థన URL:",
        "apisandbox-request-time": "అభ్యర్ధన సమయం: $1",
        "booksources": "పుస్తక మూలాలు",
        "sessionfailure-title": "సెషను వైఫల్యం",
        "sessionfailure": "మీ ప్రవేశపు సెషనుతో ఏదో సమస్య ఉన్నట్లుంది;\nసెషను హైజాకు కాకుండా ఈ చర్యను రద్దు చేసాం.\n\"back\" కొట్టి, ఎక్కడి నుండి వచ్చారో ఆ పేజీని మళ్ళీ లోడు చేసి, తిరిగి ప్రయత్నించండి.",
        "changecontentmodel-reason-label": "కారణం:",
+       "changecontentmodel-submit": "మార్చు",
        "protectlogpage": "సంరక్షణల చిట్టా",
        "protectlogtext": "ఈ క్రింద ఉన్నది పేజీల సంరక్షణలకు జరిగిన మార్పుల జాబితా.\nప్రస్తుతం అమలులో ఉన్న సంరక్షణలకై [[Special:ProtectedPages|సంరక్షిత పేజీల జాబితా]]ను చూడండి.",
        "protectedarticle": "\"[[$1]]\" సంరక్షించబడింది.",
        "confirm-watch-top": "ఈ పుటను మీ వీక్షణ జాబితాలో చేర్చాలా?",
        "confirm-unwatch-button": "సరే",
        "confirm-unwatch-top": "ఈ పుటను మీ వీక్షణ జాబితా నుండి తొలగించాలా?",
+       "confirm-rollback-button": "సరే",
        "quotation-marks": "“$1”",
        "imgmultipageprev": "← మునుపటి పేజీ",
        "imgmultipagenext": "తరువాతి పేజీ →",
        "tags-edit-title": "ట్యాగులను సవరించు",
        "tags-edit-manage-link": "ట్యాగులను నిర్వహించండి",
        "tags-edit-existing-tags": "ప్రస్తుత ట్యాగులు:",
-       "tags-edit-existing-tags-none": "''ఏమీలేవు''",
+       "tags-edit-existing-tags-none": "<em>ఏమీలేవు</em>",
        "tags-edit-new-tags": "కొత్త ట్యాగులు:",
        "tags-edit-add": "ఈ ట్యాగులను చేర్చు:",
        "tags-edit-remove": "ఈ ట్యాగులను తొలగించు:",
        "special-characters-title-emdash": "ఎమ్ డాష్",
        "special-characters-title-minus": "మైనస్ గుర్తు",
        "mw-widgets-dateinput-no-date": "ఏ తేదీనీ ఎంచుకోలేదు",
-       "mw-widgets-titleinput-description-new-page": "పేజీ ఇంకా లేదు"
+       "mw-widgets-titleinput-description-new-page": "పేజీ ఇంకా లేదు",
+       "log-action-filter-all": "అన్నీ",
+       "authmanager-realname-label": "అసలు పేరు",
+       "authmanager-realname-help": "వాడుకరి అసలు పేరు",
+       "authmanager-provider-temporarypassword": "తాత్కాలిక సంకేతపదం",
+       "credentialsform-account": "ఖాతా పేరు:"
 }
index 2c5bf98..a63aac3 100644 (file)
@@ -86,7 +86,8 @@
                        "İnternion",
                        "Hbseren",
                        "Kumkumuk",
-                       "Basak"
+                       "Basak",
+                       "Ece Alpdeniz"
                ]
        },
        "tog-underline": "Bağlantıların altını çiz:",
        "print": "Yazdır",
        "view": "Görüntüle",
        "view-foreign": "$1 üzerinde gör",
-       "edit": "Değiştir",
+       "edit": "Düzenle",
        "edit-local": "Yerel açıklamayı düzenle",
        "create": "Oluştur",
        "create-local": "Yerel açıklama ekle",
        "yourpasswordagain": "Parolayı yeniden girin:",
        "createacct-yourpasswordagain": "Parolayı onayla",
        "createacct-yourpasswordagain-ph": "Parolayı yeniden girin",
-       "remembermypassword": "Girişimi bu tarayıcıda hatırla (en fazla $1 {{PLURAL:$1|gün|gün}} için)",
        "userlogin-remembermypassword": "Oturumumu sürekli açık tut",
        "userlogin-signwithsecure": "Güvenli bağlantı kullanın",
        "cannotloginnow-title": "Şu an oturum açılamıyor",
index f022040..402a034 100644 (file)
        "yourpasswordagain": "Повторний набір пароля:",
        "createacct-yourpasswordagain": "Підтвердіть пароль",
        "createacct-yourpasswordagain-ph": "Введіть пароль знову",
-       "remembermypassword": "Запам'ятати мій обліковий запис на цьому комп'ютері (на строк не більше $1 {{PLURAL:$1|1=дня|днів}})",
        "userlogin-remembermypassword": "Запам'ятати мене",
        "userlogin-signwithsecure": "Захищене з'єднання",
        "cannotloginnow-title": "Неможливо увійти прямо зараз",
        "userpage-userdoesnotexist": "Користувач під назвою \"<nowiki>$1</nowiki>\" не зареєстрований. Переконайтеся, що ви хочете створити/редагувати цю сторінку.",
        "userpage-userdoesnotexist-view": "Обліковий запис користувача «$1» не зареєстровано.",
        "blocked-notice-logextract": "Цей користувач наразі заблокований.\nОстанній запис у журналі блокувань такий:",
-       "clearyourcache": "<strong>Увага:</strong> Після збереження слід очистити кеш оглядача, щоб побачити зміни.\n* <strong>Firefox / Safari:</strong> тримайте <em>Shift</em>, коли натискаєте <em>Оновити</em>, або натисніть <em>Ctrl-F5</em> чи <em>Ctrl-Shift-R</em> (<em>⌘-R</em> на Apple Mac)\n* <strong>Google Chrome:</strong> натисніть <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> на Apple Mac)\n* <strong>Internet Explorer:</strong> тримайте <em>Ctrl</em>, коли натискаєте <em>Оновити</em>, або натисніть <em>Ctrl-F5</em>\n* <strong>Opera:</strong> очистіть кеш за допомогою <em>Інструменти → Налаштування</em> (<em>Opera → Побажання</em> на Apple Mac) та перейдіть на <em>Привітність & безпека → очистити дані браузера → кеш</em>",
+       "clearyourcache": "<strong>Увага:</strong> Після збереження слід очистити кеш оглядача, щоб побачити зміни.\n* <strong>Firefox / Safari:</strong> тримайте <em>Shift</em>, коли натискаєте <em>Оновити</em>, або натисніть <em>Ctrl-F5</em> чи <em>Ctrl-Shift-R</em> (<em>⌘-R</em> на Apple Mac)\n* <strong>Google Chrome:</strong> натисніть <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> на Apple Mac)\n* <strong>Internet Explorer:</strong> тримайте <em>Ctrl</em>, коли натискаєте <em>Оновити</em>, або натисніть <em>Ctrl-F5</em>\n* <strong>Opera:</strong> очистіть кеш за допомогою <em>Інструменти → Налаштування</em> (<em>Opera → Побажання</em> на Apple Mac) та перейдіть на <em>Приватність & безпека → очистити дані браузера → кеш</em>",
        "usercssyoucanpreview": "'''Підказка:''' використовуйте кнопку «{{int:showpreview}}», щоб протестувати ваш новий css-файл перед збереженням.",
        "userjsyoucanpreview": "'''Підказка:''' використовуйте кнопку «{{int:showpreview}}», щоб протестувати ваш новий код JavaScript перед збереженням.",
        "usercsspreview": "'''Пам'ятайте, що це лише попередній перегляд вашого css-файлу.'''\n'''Його ще не збережено!'''",
        "filerevert-submit": "Повернути",
        "filerevert-success": "'''[[Media:$1|$1]]''' був повернутий до [$4 версії від $3, $2].",
        "filerevert-badversion": "Немає локальної версії цього файлу з вказаною поміткою дати і часу.",
+       "filerevert-identical": "Поточна версія файлу вже ідентична обраній.",
        "filedelete": "Вилучення $1",
        "filedelete-legend": "Вилучити файл",
        "filedelete-intro": "Ви збираєтесь вилучити '''[[Media:$1|$1]]''' і всю його історію.",
index 8d4d11f..55751b9 100644 (file)
        "generic-pool-error": "ہم معذرت خواہ ہیں! معیلات (سرورز) پر اِس وقت اِضافی بوجھ ہے.\nصارفین کی کثیر تعداد اِس وقت یہی صفحہ ملاحظہ کرنے کی کوشش کررہی ہے.\nبرائے مہربانی!دوبارہ کوشش کرنے سے پہلے ذرا انتظار فرمائیے.",
        "pool-errorunknown": "نامعلوم خطا",
        "poolcounter-usage-error": "استعمال میں خامی: $1",
-       "aboutsite": "تعارف {{SITENAME}}",
+       "aboutsite": "{{SITENAME}} کا تعارف",
        "aboutpage": "Project:تعارف",
        "copyright": "تمام مواد $1 کے تحت میسر ہے، جب تک کوئی دوسری وجہ نا ہو۔",
        "copyrightpage": "{{ns:project}}:حقوق تصانیف",
        "policy-url": "Project:حکمتِ عملی",
        "portal": "دیوان عام",
        "portal-url": "Project:دیوان عام",
-       "privacy": "اصÙ\88Ù\84 Ø¨Ø±Ø§Û\93 Ø§Ø®Ù\81ائÛ\92 Ø±Ø§Ø²",
+       "privacy": "اخÙ\81ائÛ\92 Ø±Ø§Ø² Ú©Û\92 Ø§ØµÙ\88Ù\84",
        "privacypage": "Project:اصولِ اخفائے راز",
        "badaccess": "خطائے اجازت",
        "badaccess-group0": "آپ متمنی عمل کا اجراء کرنے کے مُجاز نہیں۔",
        "yourpasswordagain": "کلمۂ شناخت دوبارہ لکھیں",
        "createacct-yourpasswordagain": "کلمۂ اجازت تصدیق کریں",
        "createacct-yourpasswordagain-ph": "پاس ورڈ پھر داخل کریں",
-       "remembermypassword": "اِس متصفح پر میرے داخلِ نوشتگی معلومات یاد رکھو (زیادہ سے زیادہ $1 {{PLURAL:$1|دِن|ایام}} کیلئے)",
        "userlogin-remembermypassword": "مجھے داخل رکھے",
        "userlogin-signwithsecure": "محفوظ رابطہ (کنکشن) استعمال کریں",
        "yourdomainname": "آپکا ڈومین",
        "editingold": "'''انتباہ: آپ اس صفحے کا ایک پرانا مسودہ مرتب کررہے ہیں۔ اگر آپ اسے محفوظ کرتے ہیں تو اس صفحے کے اس پرانے مسودے سے اب تک کی جانے والی تمام تدوین ضائع ہو جاۓ گی۔'''",
        "yourdiff": "تضادات",
        "copyrightwarning": "یہ یادآوری کرلیجیۓ کہ {{SITENAME}} میں تمام تحریری شراکت جی این یو آزاد مسوداتی اجازہ ($2)کے تحت تصور کی جاتی ہے (مزید تفصیل کیلیۓ $1 دیکھیۓ)۔ اگر آپ اس بات سے متفق نہیں کہ آپکی تحریر میں ترمیمات کری جائیں اور اسے آزادانہ (جیسے ضرورت ہو) استعمال کیا جاۓ تو براۓ کرم اپنی تصانیف یہاں داخل نہ کیجیۓ۔ اگر آپ یہاں اپنی تحریر جمع کراتے ہیں تو آپ اس بات کا بھی اقرار کر رہے ہیں کہ، اسے آپ نے خود تصنیف کیا ہے یا دائرہ ءعام (پبلک ڈومین) سے حاصل کیا ہے یا اس جیسے کسی اور آذاد وسیلہ سے۔'''بلااجازت ایسا کام داخل نہ کیجیۓ جسکا حق ِطبع و نشر محفوظ ہو!'''",
+       "protectedpagewarning": "<strong>انتباہ: اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔</strong>\nحوالہ کے لیے ذیل میں نوشتہ جاتی اندراج فراہم کیا گیا ہے:",
+       "semiprotectedpagewarning": "<strong>اطلاع:</strong> اس صفحہ کو یوں مقفل کیا جاچکا ہے کہ اس میں صرف اندراج شدہ صارفین ہی ترمیم کرسکتے ہیں۔\nحوالہ کے لیے ذیل میں تازہ ترین نوشتہ جاتی اندراج دیا گیا ہے:",
+       "cascadeprotectedwarning": "<strong>انتباہ:</strong> اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔ اسے مقفل کرنے کی وجہ یہ ہے کہ پیش نظر صفحہ درج ذیل محفوظ {{PLURAL:$1|صفحہ|صفحات}} کی آبشاری حفاظت میں شامل ہے:",
        "templatesused": "اِس صفحہ پر مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
        "templatesusedpreview": "اِس پیش منظر میں مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
        "templatesusedsection": "اِس قطعہ میں مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
        "viewpagelogs": "اس صفحہ کیلیے نوشتہ جات دیکھیے",
        "nohistory": "اِس صفحہ کیلئے کوئی تدوینی تاریخچہ موجود نہیں ہے.",
        "currentrev": "حـالیـہ تـجدید",
-       "currentrev-asof": "حاÙ\84Û\8cÛ\81 Ù\86ظرثاÙ\86Û\8c بمطابق $1",
-       "revisionasof": "تجدید بمطابق $1",
+       "currentrev-asof": "حاÙ\84Û\8cÛ\81 Ù\86سخÛ\81 بمطابق $1",
+       "revisionasof": "نسخہ بمطابق $1",
        "revision-info": "نظرثانی بتاریخ $1 از {{GENDER:$6|$2}}$7",
        "previousrevision": "←پرانی تدوین",
        "nextrevision": "→اگلا اعادہ",
        "history-feed-description": "ویکی پر اِس صفحہ کا تاریخچۂ نظرثانی",
        "history-feed-item-nocomment": "بہ $2 $1",
        "history-feed-empty": "درخواست شدہ صفحہ موجود نہیں.\nیا تو یہ ویکی سے حذف کیا گیا ہے اور یا اِس کا نام تبدیل کردیا گیا ہے.\nآپ متعلقہ نئے صفحات کیلئے [[Special:Search|ویکی پر تلاش]] کرسکتے ہیں.",
+       "history-edit-tags": "منتخب نظرثانیوں کے ٹیگوں میں ترمیم کریں",
        "rev-deleted-comment": "(تبصرہ حذف کی گيا ہے)",
        "rev-deleted-user": "(صارف نام حذف کیا گيا ہے)",
        "rev-delundel": "دکھاؤ/چھپاؤ",
        "recentchanges-legend-heading": "<strong>اختیارات</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (نیز [[Special:NewPages|جدید صفحات کی فہرست]]) ملاحظہ فرمائیں",
        "recentchanges-submit": "دکھائیں",
-       "rcnotefrom": "ذیل میں <strong>$3, $4</strong> سے کی گئی {{PLURAL:$5|تبدیلی|تبدیلیاں}} <strong>$1</strong> تک دکھائی جا رہی ہیں۔",
-       "rclistfrom": "$3 $2 سےنئی تبدیلیاں دکھانا شروع کریں",
+       "rcnotefrom": "ذیل میں <strong>$2</strong> سے کی گئی {{PLURAL:$5|تبدیلی|تبدیلیاں}} <strong>$1</strong> تک دکھائی جا رہی ہیں۔",
+       "rclistfrom": "$2، $3ء سے ہونے والی نئی تبدیلیاں دکھائیں",
        "rcshowhideminor": "معمولی ترامیم $1",
        "rcshowhideminor-show": "دکھائیں",
        "rcshowhideminor-hide": "چھپائیں",
        "speciallogtitlelabel": "ہدف (عنوان یا {{ns:user}}:صارف نام برائے صارف):",
        "log": "نوشتہ جات",
        "logeventslist-submit": "دکھائیں",
+       "checkbox-select": "$1 کو منتخب کریں",
+       "checkbox-all": "سب",
+       "checkbox-none": "کچھ نہیں",
+       "checkbox-invert": "برعکس",
        "allpages": "تمام صفحات",
        "nextpage": "اگلا صفحہ ($1)",
        "prevpage": "پچھلا صفحہ ($1)",
        "deleteotherreason": "دوسری/اِضافی وجہ:",
        "deletereasonotherlist": "دوسری وجہ",
        "rollback": "ترمیمات سابقہ حالت پرواپس",
-       "rollbacklink": "واپس سابقہ حالت",
+       "rollbacklink": "استرجع کریں",
        "rollbacklinkcount": "استرجع $1 {{PLURAL:$1|ترمیم|ترامیم}}",
+       "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|ترمیم|ترامیم}} سے زیادہ کا استرجع",
        "rollbackfailed": "سابقہ حالت پر واپسی ناکام",
        "cantrollback": "تدوین ثانی کا اعادہ نہیں کیا جاسکتا؛ کیونکہ اس میں آخری بار حصہ لینے والا ہی اس صفحہ کا واحد کاتب ہے۔",
        "changecontentmodel-title-label": "صفحہ کا عنوان",
        "undelete-search-submit": "تلاش",
        "undelete-no-results": "حذف شدہ صفحات میں ایسا کوئی صفحہ نہیں ملا",
        "undelete-show-file-submit": "ہاں",
-       "namespace": "Ù\81ضائÛ\92 Ù\86اÙ\85:",
+       "namespace": "Ù\86اÙ\85 Ù\81ضا:",
        "invert": "انتخاب بالعکس",
        "tooltip-invert": "منتخب شدہ فضائے نام (اور مُلحقہ فضائے نام) میں شامل صفحات کی تبدیلیوں کو چُھپانے کیلئے اِس خانہ کو ٹِک کریں۔",
-       "namespace_association": "متعلقہ فضا",
+       "namespace_association": "Ù\85تعÙ\84Ù\82Û\81 Ù\86اÙ\85 Ù\81ضا",
        "blanknamespace": "(مرکز)",
        "contributions": "{{GENDER:$1|صارف}} شراکتیں",
        "contributions-title": "مساہماتِ صارف برائے $1",
        "blocklink": "پابندی لگائیں",
        "unblocklink": "پابندی ختم",
        "change-blocklink": "پابندی میں تبدیلی",
-       "contribslink": "شراکت",
+       "contribslink": "شراکتیں",
        "blocklogpage": "نوشتۂ پابندی",
        "block-log-flags-nocreate": "کھاتے کی تخلیق غیرفعال",
        "move-page": "منتقلی $1",
        "tooltip-rollback": "پچھلے صارف کی کی گئی اِس صفحے پر استرجع شدہ ترامیم کو ایک کلِک میں واپس کریں",
        "tooltip-undo": "''استرجع'' اس ترمیم کو پچھلی ترمیم کے جانب واپس کردیگا اور نمائشی انداز میں خانہ ترمیم کھول دے گا۔ آپ مختصراً سبب بیان کرنے کے بھی مجاز ہونگے۔",
        "tooltip-summary": "مختصر خلاصہ درج کریں",
+       "common.css": "body,\ntextarea {\n    font-family: Amiri;\n}",
        "anonymous": "{{SITENAME}} گمنام صارف",
        "others": "دیگر",
        "pageinfo-visiting-watchers": "تعداد ناظرین جنہوں نے حالیہ ترامیم کا مشاہدہ کیا",
        "pageinfo-hidden-categories": "پوشیدہ {{PLURAL:$1|زمرہ|زمرہ جات}} ($1)",
        "pageinfo-toolboxlink": "معلومات صفحہ",
+       "pageinfo-category-info": "زمرے کی معلومات",
+       "pageinfo-category-pages": "تعداد صفحات",
+       "pageinfo-category-subcats": "تعداد ذیلی زمرہ جات",
+       "pageinfo-category-files": "تعداد املاف",
        "markaspatrolledtext": "اس صفحہ کو بطور مراجعت شدہ نشان زد کریں",
        "markedaspatrollederrornotify": "بطور مراجعت نشان زد نہیں کیا جا سکا۔",
        "deletedrevision": "حذف شدہ پرانی ترمیم $1۔",
index 92c5a16..31b213f 100644 (file)
        "yourpasswordagain": "Gõ lại mật khẩu",
        "createacct-yourpasswordagain": "Xác nhận lại mật khẩu",
        "createacct-yourpasswordagain-ph": "Nhập mật khẩu lần nữa",
-       "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)",
        "userlogin-remembermypassword": "Giữ trạng thái đăng nhập",
        "userlogin-signwithsecure": "Sử dụng kết nối an toàn",
        "cannotloginnow-title": "Không thể đăng nhập lúc này",
        "file-thumbnail-no": "Tên tập tin bắt đầu bằng <strong>$1</strong>.\nCó vẻ đây là bản thu nhỏ của hình gốc ''(thumbnail)''.\nNếu bạn có hình ở độ phân giải tối đa, xin hãy tải bản đó lên, nếu không xin hãy đổi lại tên tập tin.",
        "fileexists-forbidden": "Đã có tập tin với tên gọi này, và nó không thể bị ghi đè.\nNếu bạn vẫn muốn tải tập tin của bạn lên, xin hãy quay lại và sử dụng một tên khác. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Một tập tin với tên này đã tồn tại ở kho tập tin dùng chung.\nNếu bạn vẫn muốn tải tập tin của bạn lên, xin hãy quay lại và dùng một tên khác. [[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Tập tin tải lên này là bản sao y hệt với phiên bản hiện tại của <strong>[[:$1]]</strong>.",
+       "fileexists-duplicate-version": "Tập tin tải lên này là bản sao y hệt với {{PLURAL:$2|một phiên bản trước đây|các phiên bản trước đây}} của <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Tập tin này có vẻ là bản sao của {{PLURAL:$1|tập tin|các  tập tin}} sau:",
        "file-deleted-duplicate": "Một tập tin giống hệt như tập tin này ([[:$1]]) đã từng bị xóa trước đây. Bạn nên xem lại lịch sử xóa tập tin trước khi tiếp tục tải nó lên lại.",
        "file-deleted-duplicate-notitle": "Một tập tin giống hệt như tập tin này đã từng bị xóa và tên bị xóa hẳn trước đây.\nBạn nên xin một người có quyền xem dữ liệu tập tin bị xóa hẳn xem lại trường hợp này trước khi tiếp tục tải nó lên lại.",
        "filerevert-submit": "Lùi lại",
        "filerevert-success": "'''[[Media:$1|$1]]''' đã được lùi về [$4 phiên bản lúc $3, $2].",
        "filerevert-badversion": "Không tồn tại phiên bản trước đó của tập tin tại thời điểm trên.",
+       "filerevert-identical": "Phiên bản hiện tại của tập tin đã y hệt với phiên bản được chọn.",
        "filedelete": "Xóa $1",
        "filedelete-legend": "Xóa tập tin",
        "filedelete-intro": "Bạn sắp xóa tập tin '''[[Media:$1|$1]]''' cùng với tất cả lịch sử của nó.",
index c194377..71f37b9 100644 (file)
        "yourpasswordagain": "Klavolös dönu letavödi:",
        "createacct-yourpasswordagain": "Fümedolös letavödi",
        "createacct-yourpasswordagain-ph": "Penolös letavödi dönu",
-       "remembermypassword": "Dakipolöd ninädamanünis obik in nünöm at (muiko {{PLURAL:$1|del|dels}} $1)",
        "userlogin-remembermypassword": "Dakipön obi penunädöl",
        "yourdomainname": "Domen olik:",
        "password-change-forbidden": "No kanol votükön letavödis su el wiki at.",
index 8c65987..9937693 100644 (file)
        "yourpassword": "Vosse sicret",
        "userlogin-yourpassword": "Sicret",
        "yourpasswordagain": "Ritapez vosse sicret",
-       "remembermypassword": "Rimimbrer m' sicret inte les sessions (nén dpus ki po $1 {{PLURAL:$1|djoû|djoûs}})",
        "yourdomainname": "Vosse dominne",
        "login": "S' elodjî",
        "nav-login-createaccount": "Ahiver on conte, udon-bén s' elodjî",
index 51b0dd4..803c6a2 100644 (file)
        "yourpasswordagain": "ווידער אריינקלאפן פאסווארט",
        "createacct-yourpasswordagain": "באשטעטיקן פאסווארט",
        "createacct-yourpasswordagain-ph": "ארײַנגעבן פאסווארט נאכאמאל",
-       "remembermypassword": "געדענקען מיין אַריינלאָגירן אין דעם דאָזיקן בראַוזער (ביז $1 {{PLURAL:$1|טאָג|טעג}})",
        "userlogin-remembermypassword": "לאז מיך בלײַבן ארײַנלאגירט",
        "userlogin-signwithsecure": "ניצן זיכערן סארווער",
        "cannotloginnow-title": "קען נישט אריינלאגירן אצינד",
index 5a2986f..84ec923 100644 (file)
        "yourpasswordagain": "再輸入密碼:",
        "createacct-yourpasswordagain": "確認密碼",
        "createacct-yourpasswordagain-ph": "入多次密碼",
-       "remembermypassword": "響呢個瀏覽器度記住我嘅登入資料 (最高維持$1{{PLURAL:$1|日|日}})",
        "userlogin-remembermypassword": "記住我有簽到",
        "userlogin-signwithsecure": "用安全連線",
        "yourdomainname": "你嘅網域:",
        "suppressionlog": "廢止日誌",
        "suppressionlogtext": "下面係刪除同埋由操作員牽涉到內容封鎖嘅一覽。\n睇吓[[Special:BlockList|封鎖一覽]]去睇現時進行緊嘅禁止同埋封鎖表。",
        "mergehistory": "合併頁歷史",
-       "mergehistory-header": "呢一版可以畀你去合併一個來源頁嘅修訂記錄到另一個新頁。\n請確認呢次更改會繼續保留嗰版之前嘅歷史。",
+       "mergehistory-header": "呢一版可以畀你去合併一個來源頁嘅修訂記錄到另一個新頁。\n請確認呢次更改會繼續保留嗰版之前嘅歷史連續性。",
        "mergehistory-box": "合併兩版嘅修訂:",
        "mergehistory-from": "來源頁:",
        "mergehistory-into": "目的頁:",
index b4e753a..2d38231 100644 (file)
        "yourpasswordagain": "请再次输入密码:",
        "createacct-yourpasswordagain": "确认密码",
        "createacct-yourpasswordagain-ph": "请再次输入密码",
-       "remembermypassword": "在该浏览器记住我的登录状态(最长$1天)",
        "userlogin-remembermypassword": "记住我的登录状态",
        "userlogin-signwithsecure": "使用安全连接",
+       "cannotlogin-title": "不能登录",
+       "cannotlogin-text": "无法登录。",
        "cannotloginnow-title": "现在不能登录",
        "cannotloginnow-text": "当使用$1时无法登录。",
+       "cannotcreateaccount-title": "无法创建账户",
+       "cannotcreateaccount-text": "此wiki没有启用直接账户创建。",
        "yourdomainname": "您的域名:",
        "password-change-forbidden": "您不能在本wiki上更改密码。",
        "externaldberror": "验证数据库出错或您被禁止更新您的外部账号。",
        "changeemail-newemail": "新的电子邮件地址:",
        "changeemail-newemail-help": "此字段应留空,如果您希望移除您的电子邮件地址的话。如果电子邮件地址被移除,您将无法重置忘记的密码,并将不会接收来自此wiki的电子邮件。",
        "changeemail-none": "(无)",
-       "changeemail-password": "的{{SITENAME}}密码:",
+       "changeemail-password": "的{{SITENAME}}密码:",
        "changeemail-submit": "更改电子邮件地址",
        "changeemail-throttled": "您最近尝试了太多次登录。请等待$1后再试。",
        "changeemail-nochange": "请输入一个不同的新的电子邮件地址。",
        "file-thumbnail-no": "文件名以<strong>$1</strong>开始。它似乎是缩小的图像<em>(缩略图)</em>。如果您有完整分辨率的该图像,请上传它,否则请更改文件名。",
        "fileexists-forbidden": "已存在相同名称的文件,且不能覆盖;请返回并用一个新的名称来上传此文件。[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "共享文件库中存在该名称的文件。如果您仍想上传你的文件,请返回使用其他名称。[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "上传的文件与<strong>[[:$1]]</strong>的当前版本完全相同。",
+       "fileexists-duplicate-version": "上传的文件与<strong>[[:$1]]</strong>的{{PLURAL:$2|旧版本}}完全相同。",
        "file-exists-duplicate": "本文件是以下{{PLURAL:$1|文件}}的副本:",
        "file-deleted-duplicate": "一个相同名称的文件 ([[:$1]]) 在先前删除过。您应该在重新上传之前检查一下该文件之删除纪录。",
        "file-deleted-duplicate-notitle": "之前有与此相同的文件被删除和取消标题。您应该询问查看过改文件数据的任何人以复查重新上传时的诸多问题。",
        "filerevert-submit": "恢复",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong>已经恢复至[$4 $2 $3的版本]。",
        "filerevert-badversion": "文件并无所请求时间戳下的早期本地版本。",
+       "filerevert-identical": "文件的当前版本已与选择的版本相同。",
        "filedelete": "删除$1",
        "filedelete-legend": "删除文件",
        "filedelete-intro": "您将要删除文件<strong>[[Media:$1|$1]]</strong>及其全部历史。",
        "pageinfo-article-id": "页面ID",
        "pageinfo-language": "页面内容语言",
        "pageinfo-content-model": "页面内容类型",
+       "pageinfo-content-model-change": "更改",
        "pageinfo-robot-policy": "爬虫索引",
        "pageinfo-robot-index": "允许",
        "pageinfo-robot-noindex": "不允许",
        "logentry-delete-revision": "$1{{GENDER:$2|更改}}页面$3的{{PLURAL:$5|$5个版本}}的可见性:$4",
        "logentry-delete-event-legacy": "$1{{GENDER:$2|更改}}$3的日志事件的可见性",
        "logentry-delete-revision-legacy": "$1{{GENDER:$2|更改}}页面$3的版本的可见性",
-       "logentry-suppress-delete": "$1{{GENDER:$2|已隐藏}}页面$3",
+       "logentry-suppress-delete": "$1{{GENDER:$2|已屏蔽}}页面$3",
        "logentry-suppress-event": "$1秘密地{{GENDER:$2|更改}}$3的{{PLURAL:$5|$5个日志事件}}的可见性:$4",
        "logentry-suppress-revision": "$1秘密地{{GENDER:$2|更改}}页面$3的{{PLURAL:$5|$5个版本}}的可见性:$4",
        "logentry-suppress-event-legacy": "$1秘密地{{GENDER:$2|更改}}$3的日志事件的可见性",
index 0b01858..cd86b2d 100644 (file)
        "yourpasswordagain": "再輸入密碼一次:",
        "createacct-yourpasswordagain": "確認密碼",
        "createacct-yourpasswordagain-ph": "再次輸入密碼",
-       "remembermypassword": "在瀏覽器上記住我的登入資訊 (上限 $1 {{PLURAL:$1|天}})",
        "userlogin-remembermypassword": "記住我的登入狀態",
        "userlogin-signwithsecure": "使用安全連線",
        "cannotloginnow-title": "現在無法登入",
        "suppressionlog": "禁止顯示日誌",
        "suppressionlogtext": "以下清單為管理員透過刪除或封鎖所隱藏的內容。\n請至 [[Special:BlockList|封鎖清單]] 取得目前已封鎖的清單。",
        "mergehistory": "合併頁面歷史",
-       "mergehistory-header": "這頁可以讓您合併一個來源頁面的歷史到另一個新頁面中。\n請確認這次更改會繼續保留該頁面先前的歷史版本。",
+       "mergehistory-header": "這頁可以讓您合併一個來源頁面的歷史到另一個新頁面中。\n請確認這次更改能夠繼續保留該頁面先前歷史版本的連續性。",
        "mergehistory-box": "合併兩個頁面的修訂:",
        "mergehistory-from": "來源頁面:",
        "mergehistory-into": "目標頁面:",
        "grant-group-high-volume": "執行大量活動",
        "grant-group-customization": "自訂與偏好設定",
        "grant-group-administration": "執行管理操作",
+       "grant-group-private-information": "存取關於您的隱私資料",
        "grant-group-other": "其他活動",
        "grant-blockusers": "封鎖與解除封鎖使用者",
        "grant-createaccount": "建立帳號",
        "grant-highvolume": "大量編輯",
        "grant-oversight": "隱藏使用者和禁止顯示修訂",
        "grant-patrol": "巡邏頁面的變更",
+       "grant-privateinfo": "存取隱私資訊",
        "grant-protect": "保護與取消保護頁面",
        "grant-rollback": "還原頁面的變更",
        "grant-sendemail": "傳送電子郵件聯絡其他使用者",
        "file-thumbnail-no": "檔案名稱以 <strong>$1</strong> 為開頭。\n似乎已為縮小的圖片 <em>(縮圖)</em>。\n若您有原始大小的圖片,應上傳原始圖片,否則請變更檔名稱。",
        "fileexists-forbidden": "已存在相同名稱的檔案,且無法覆蓋。\n若您仍要上傳此檔案,請返回上一頁並使用其他名稱。\n[[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "共用檔案庫中已存在此名稱的檔案。\n若您仍要上傳此檔案,請返回上一頁並使用其他名稱。\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "上傳的檔案與目前版本的 <strong>[[:$1]]</strong> 完全相同。",
+       "fileexists-duplicate-version": "上傳的檔案與{{PLURAL:$2|較舊版本|較舊版本}}的 <strong>[[:$1]]</strong> 完全相同。",
        "file-exists-duplicate": "此檔案與下列{{PLURAL:$1|一|多}}個檔案重複:",
        "file-deleted-duplicate": "與此檔案完全相同的檔案 ([[:$1]]) 在先前已被刪除。\n您應在重新上傳之前確認該檔案的刪除日誌。",
        "file-deleted-duplicate-notitle": "與此檔案完全相同的檔案在先前已被刪除,且禁止顯示該標題。\n您在重新上傳前,應請求有權力檢視隱藏檔案的使用者重新審查。",
        "filerevert-submit": "還原",
        "filerevert-success": "<strong>[[Media:$1|$1]]</strong> 已經還原到 [$4 於 $2 $3 的版本]。",
        "filerevert-badversion": "查無此檔案先前於指定時間的本地版本。",
+       "filerevert-identical": "目前版本的檔案與選擇的版本完全相同。",
        "filedelete": "刪除 $1",
        "filedelete-legend": "刪除檔案",
        "filedelete-intro": "您現正要刪除檔案 <strong>[[Media:$1|$1]]</strong> 與其所有歷史版本。",
        "nrevisions": "$1 次修訂",
        "nimagelinks": "被 $1 個頁面使用",
        "ntransclusions": "被 $1 個頁面使用",
-       "specialpage-empty": "此報表查無任何結果。",
+       "specialpage-empty": "此報表查無任何結果。",
        "lonelypages": "孤立頁面",
        "lonelypagestext": "下列頁面尚未被 {{SITENAME}} 中的其它頁面連結或引用。",
        "uncategorizedpages": "未分類的頁面",
        "watchnologin": "尚未登入",
        "addwatch": "新增至監視清單",
        "addedwatchtext": "已於[[Special:Watchlist|您的監視清單]]新增頁面 \"[[:$1]]\" 及其討論頁面。\n未來對此頁面及其關聯的對話頁面的變更將會在此清單中列出。",
+       "addedwatchtext-talk": "\"[[:$1]]\" 及相關的頁面已加入至您的 [[Special:Watchlist|監視清單]]。",
        "addedwatchtext-short": "已於您的監視清單新增頁面 \"$1\"。",
        "removewatch": "從監視清單中移除",
        "removedwatchtext": "已於[[Special:Watchlist|您的監視清單]]移除頁面 \"[[:$1]]\" 及其討論頁面。",
+       "removedwatchtext-talk": "已自您的 [[Special:Watchlist|監視清單]] 移除 \"[[:$1]]\" 及相關的頁面。",
        "removedwatchtext-short": "已於您的監視清單移除頁面 \"$1\"。",
        "watch": "監視",
        "watchthispage": "監視此頁面",
        "rollbacklinkcount-morethan": "還原超過 $1 次{{PLURAL:$1|編輯}}",
        "rollbackfailed": "還原失敗",
        "rollback-missingparam": "請求缺少必要參數。",
+       "rollback-missingrevision": "無法載入修訂資料。",
        "cantrollback": "無法還原編輯;\n此頁面的最後貢獻者是唯一的作者。",
        "alreadyrolled": "無法還原由 [[User:$2|$2]] ([[User talk:$2|對話]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]] 所作的最後一次編輯 [[:$1]],已有其他人編輯或還原了該頁面。\n\n最後一次編輯該頁面的使用者是 [[User:$3|$3]] ([[User talk:$3|對話]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]])。",
        "editcomment": "編輯摘要為:<em>$1</em>。",
        "undeletehistorynoadmin": "已刪除此頁面。\n以下摘要顯示刪除原因與刪除前所有編輯過此頁面的使用者詳細資料。\n這些已刪除的實際文字修訂僅對管理員可用。",
        "undelete-revision": "被 $3 刪除的 $1 (於 $4 $5) 修訂:",
        "undeleterevision-missing": "無效或遺失的修訂。\n您可能使用了錯誤的連結,或該修訂已從封存中還原或刪除。",
+       "undeleterevision-duplicate-revid": "無法還原 {{PLURAL:$1|1 個修訂|$1 修訂}},因{{PLURAL:$1|修訂的|修訂的}} <code>rev_id</code> 已在使用中。",
        "undelete-nodiff": "查無先前的修訂。",
        "undeletebtn": "還原",
        "undeletelink": "檢視/還原",
        "undeletedrevisions": "{{PLURAL:$1|$1 個修訂}}已還原",
        "undeletedrevisions-files": "{{PLURAL:$1|$1 個修訂}}與 {{PLURAL:$2|$2 個檔案}}已還原",
        "undeletedfiles": "{{PLURAL:$1|$1}} 個檔案已還原",
-       "cannotundelete": "取消刪除失敗:\n$1",
+       "cannotundelete": "部份或全部的取消刪除失敗:\n$1",
        "undeletedpage": "<strong>$1 已還原</strong>\n\n請參考 [[Special:Log/delete|刪除日誌]] 以查詢最近刪除及還原的記錄。",
        "undelete-header": "請參考 [[Special:Log/delete|刪除日誌]] 查詢最近刪除的頁面。",
        "undelete-search-title": "搜尋已刪除頁面",
        "linkaccounts-submit": "連結帳號",
        "unlinkaccounts": "取消連結帳號",
        "unlinkaccounts-success": "已取消連結帳號。",
-       "authenticationdatachange-ignored": "認証資料變更未被處理,可能未設定提供者?"
+       "authenticationdatachange-ignored": "認証資料變更未被處理,可能未設定提供者?",
+       "userjsispublic": "請注意:JavaScript 子頁面可被其他使用者檢視,不應包含憑証資料。",
+       "usercssispublic": "請注意:CSS 子頁面可被其他使用者檢視,不應包含憑証資料。"
 }
index 698fd9a..6e1f741 100644 (file)
@@ -109,7 +109,7 @@ abstract class Maintenance {
        private $mDb = null;
 
        /** @var float UNIX timestamp */
-       private $lastSlaveWait = 0.0;
+       private $lastReplicationWait = 0.0;
 
        /**
         * Used when creating separate schema files.
@@ -1227,7 +1227,7 @@ abstract class Maintenance {
         * If not set, wfGetDB() will be used.
         * This function has the same parameters as wfGetDB()
         *
-        * @param integer $db DB index (DB_SLAVE/DB_MASTER)
+        * @param integer $db DB index (DB_REPLICA/DB_MASTER)
         * @param array $groups; default: empty array
         * @param string|bool $wiki; default: current wiki
         * @return IDatabase
@@ -1264,23 +1264,29 @@ abstract class Maintenance {
        }
 
        /**
-        * Commit the transcation on a DB handle and wait for slaves to catch up
+        * Commit the transcation on a DB handle and wait for replica DBs to catch up
         *
         * This method makes it clear that commit() is called from a maintenance script,
         * which has outermost scope. This is safe, unlike $dbw->commit() called in other places.
         *
         * @param IDatabase $dbw
         * @param string $fname Caller name
-        * @return bool Whether the slave wait succeeded
+        * @return bool Whether the replica DB wait succeeded
         * @since 1.27
         */
        protected function commitTransaction( IDatabase $dbw, $fname ) {
                $dbw->commit( $fname );
+               try {
+                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                       $lbFactory->waitForReplication(
+                               [ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
+                       );
+                       $this->lastReplicationWait = microtime( true );
 
-               $ok = wfWaitForSlaves( $this->lastSlaveWait, false, '*', 30 );
-               $this->lastSlaveWait = microtime( true );
-
-               return $ok;
+                       return true;
+               } catch ( DBReplicationWaitError $e ) {
+                       return false;
+               }
        }
 
        /**
index 4947cb1..a39ac0a 100644 (file)
@@ -1,7 +1,7 @@
 -- Split user table into two parts:
 --   user
 --   user_rights
--- The later contains only the permissions of the user. This way,
+-- The latter contains only the permissions of the user. This way,
 -- you can store the accounts for several wikis in one central
 -- database but keep user rights local to the wiki.
 
index db3af92..38daf64 100644 (file)
@@ -302,7 +302,7 @@ class BackupDumper extends Maintenance {
 
                $dbr = $this->forcedDb;
                if ( $this->forcedDb === null ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                }
                $this->maxCount = $dbr->selectField( $table, "MAX($field)", '', __METHOD__ );
                $this->startTime = microtime( true );
@@ -322,7 +322,7 @@ class BackupDumper extends Maintenance {
                }
 
                $this->lb = wfGetLBFactory()->newMainLB();
-               $db = $this->lb->getConnection( DB_SLAVE, 'dump' );
+               $db = $this->lb->getConnection( DB_REPLICA, 'dump' );
 
                // Discourage the server from disconnecting us if it takes a long time
                // to read out the big ol' batch query.
index 0a89bfa..884e307 100644 (file)
@@ -118,7 +118,7 @@ class BenchmarkParse extends Maintenance {
         * @return bool|string Revision ID, or false if not found or error
         */
        function getRevIdForTime( Title $title, $timestamp ) {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                $id = $dbr->selectField(
                        [ 'revision', 'page' ],
index 097ad1f..6eafc96 100644 (file)
@@ -36,7 +36,7 @@ class CheckBadRedirects extends Maintenance {
 
        public function execute() {
                $this->output( "Fetching redirects...\n" );
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $result = $dbr->select(
                        [ 'page' ],
                        [ 'page_namespace', 'page_title', 'page_latest' ],
index f05d15c..3e57393 100644 (file)
@@ -37,7 +37,7 @@ class CheckImages extends Maintenance {
 
        public function execute() {
                $start = '';
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                $numImages = 0;
                $numGood = 0;
index 6c66da4..e6d9547 100644 (file)
@@ -40,7 +40,7 @@ class CheckUsernames extends Maintenance {
        }
 
        function execute() {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                $maxUserId = 0;
                do {
index 2f3f013..4e47cfb 100644 (file)
@@ -64,7 +64,7 @@ class CleanupSpam extends Maintenance {
                        $this->output( "Finding spam on " . count( $wgLocalDatabases ) . " wikis\n" );
                        $found = false;
                        foreach ( $wgLocalDatabases as $wikiID ) {
-                               $dbr = $this->getDB( DB_SLAVE, [], $wikiID );
+                               $dbr = $this->getDB( DB_REPLICA, [], $wikiID );
 
                                $count = $dbr->selectField( 'externallinks', 'COUNT(*)',
                                        [ 'el_index' . $dbr->buildLike( $like ) ], __METHOD__ );
@@ -83,7 +83,7 @@ class CleanupSpam extends Maintenance {
                } else {
                        // Clean up spam on this wiki
 
-                       $dbr = $this->getDB( DB_SLAVE );
+                       $dbr = $this->getDB( DB_REPLICA );
                        $res = $dbr->select( 'externallinks', [ 'DISTINCT el_from' ],
                                [ 'el_index' . $dbr->buildLike( $like ) ], __METHOD__ );
                        $count = $dbr->numRows( $res );
index a18b81e..3ace09c 100644 (file)
@@ -106,7 +106,7 @@ class TableCleanup extends Maintenance {
         * @throws MWException
         */
        public function runTable( $params ) {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                if ( array_diff( array_keys( $params ),
                        [ 'table', 'conds', 'index', 'callback' ] )
index 4f23ef0..650fae0 100644 (file)
@@ -78,7 +78,7 @@ class TitleCleanup extends TableCleanup {
        protected function fileExists( $name ) {
                // XXX: Doesn't actually check for file existence, just presence of image record.
                // This is reasonable, since cleanupImages.php only iterates over the image table.
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $row = $dbr->selectRow( 'image', [ 'img_name' ], [ 'img_name' => $name ], __METHOD__ );
 
                return $row !== false;
index 13b239f..ce19974 100644 (file)
@@ -37,7 +37,7 @@ class ClearInterwikiCache extends Maintenance {
 
        public function execute() {
                global $wgLocalDatabases, $wgMemc;
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $res = $dbr->select( 'interwiki', [ 'iw_prefix' ], false );
                $prefixes = [];
                foreach ( $res as $row ) {
index 245b613..8bd060f 100644 (file)
@@ -35,7 +35,7 @@ class CompareParserCache extends Maintenance {
        public function execute() {
                $pages = $this->getOption( 'maxpages' );
 
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                $totalsec = 0.0;
                $scanned = 0;
index 35a7ca7..69f4f89 100644 (file)
@@ -41,7 +41,7 @@ class DeleteDefaultMessages extends Maintenance {
                global $wgUser;
 
                $this->output( "Checking existence of old default messages..." );
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $res = $dbr->select( [ 'page', 'revision' ],
                        [ 'page_namespace', 'page_title' ],
                        [
index 1faeb8a..ff4e894 100644 (file)
@@ -44,7 +44,7 @@ class DumpLinks extends Maintenance {
        }
 
        public function execute() {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $result = $dbr->select( [ 'pagelinks', 'page' ],
                        [
                                'page_id',
index cfb59c6..d0bda4e 100644 (file)
@@ -222,7 +222,7 @@ TEXT
 
                // 2. The Connection, through the load balancer.
                try {
-                       $this->db = $this->lb->getConnection( DB_SLAVE, 'dump' );
+                       $this->db = $this->lb->getConnection( DB_REPLICA, 'dump' );
                } catch ( Exception $e ) {
                        throw new MWException( __METHOD__
                                . " rotating DB failed to obtain new database (" . $e->getMessage() . ")" );
index 5b446d8..8d63fe5 100644 (file)
@@ -76,7 +76,7 @@ By default, outputs relative paths against the parent directory of $wgUploadDire
         * @param bool $shared True to pass shared-dir settings to hash func
         */
        function fetchUsed( $shared ) {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $image = $dbr->tableName( 'image' );
                $imagelinks = $dbr->tableName( 'imagelinks' );
 
@@ -97,7 +97,7 @@ By default, outputs relative paths against the parent directory of $wgUploadDire
         * @param bool $shared True to pass shared-dir settings to hash func
         */
        function fetchLocal( $shared ) {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $result = $dbr->select( 'image',
                        [ 'img_name' ],
                        '',
index 7e1b527..2ed1efa 100644 (file)
@@ -49,7 +49,7 @@ class FetchText extends Maintenance {
         * note that the text string itself is *not* followed by newline
         */
        public function execute() {
-               $db = $this->getDB( DB_SLAVE );
+               $db = $this->getDB( DB_REPLICA );
                $stdin = $this->getStdin();
                while ( !feof( $stdin ) ) {
                        $line = fgets( $stdin );
index 232151b..460b553 100644 (file)
@@ -47,7 +47,7 @@ class FixDefaultJsonContentPages extends LoggedUpdateMaintenance {
                        return true;
                }
 
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $namespaces = [
                        NS_MEDIAWIKI => $dbr->buildLike( $dbr->anyString(), '.json' ),
                        NS_USER => $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString(), '.json' ),
index 0592d2d..1d6f31d 100644 (file)
@@ -54,7 +54,7 @@ class FixDoubleRedirects extends Maintenance {
                        $title = null;
                }
 
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                // See also SpecialDoubleRedirects
                $tables = [
index f4674cb..37fd44f 100644 (file)
@@ -80,7 +80,7 @@ class FixUserRegistration extends Maintenance {
                                        $this->output( "Could not find registration for #$id NULL\n" );
                                }
                        }
-                       $this->output( "Waiting for slaves..." );
+                       $this->output( "Waiting for replica DBs..." );
                        wfWaitForSlaves();
                        $this->output( " done.\n" );
                } while ( $res->numRows() >= $this->mBatchSize );
index f5cc6f6..87af5b8 100644 (file)
@@ -113,7 +113,7 @@ class GenerateSitemap extends Maintenance {
        public $timestamp;
 
        /**
-        * A database slave object
+        * A database replica DB object
         *
         * @var object
         */
@@ -196,7 +196,7 @@ class GenerateSitemap extends Maintenance {
                $this->identifier = $this->getOption( 'identifier', wfWikiID() );
                $this->compress = $this->getOption( 'compress', 'yes' ) !== 'no';
                $this->skipRedirects = $this->getOption( 'skip-redirects', false ) !== false;
-               $this->dbr = $this->getDB( DB_SLAVE );
+               $this->dbr = $this->getDB( DB_REPLICA );
                $this->generateNamespaces();
                $this->timestamp = wfTimestamp( TS_ISO_8601, wfTimestampNow() );
                $this->findex = fopen( "{$this->fspath}sitemap-index-{$this->identifier}.xml", 'wb' );
diff --git a/maintenance/getReplicaServer.php b/maintenance/getReplicaServer.php
new file mode 100644 (file)
index 0000000..6e0a1fe
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Reports the hostname of a replica DB server.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Maintenance script that reports the hostname of a replica DB server.
+ *
+ * @ingroup Maintenance
+ */
+class GetSlaveServer extends Maintenance {
+       public function __construct() {
+               parent::__construct();
+               $this->addOption( "group", "Query group to check specifically" );
+               $this->addDescription( 'Report the hostname of a replica DB server' );
+       }
+
+       public function execute() {
+               global $wgAllDBsAreLocalhost;
+               if ( $wgAllDBsAreLocalhost ) {
+                       $host = 'localhost';
+               } elseif ( $this->hasOption( 'group' ) ) {
+                       $db = $this->getDB( DB_REPLICA, $this->getOption( 'group' ) );
+                       $host = $db->getServer();
+               } else {
+                       $lb = wfGetLB();
+                       $i = $lb->getReaderIndex();
+                       $host = $lb->getServerName( $i );
+               }
+               $this->output( "$host\n" );
+       }
+}
+
+$maintClass = "GetSlaveServer";
+require_once RUN_MAINTENANCE_IF_MAIN;
index 81228cc..2160928 100644 (file)
@@ -1,55 +1,3 @@
 <?php
-/**
- * Reports the hostname of a slave server.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Maintenance
- */
-
-require_once __DIR__ . '/Maintenance.php';
-
-/**
- * Maintenance script that reports the hostname of a slave server.
- *
- * @ingroup Maintenance
- */
-class GetSlaveServer extends Maintenance {
-       public function __construct() {
-               parent::__construct();
-               $this->addOption( "group", "Query group to check specifically" );
-               $this->addDescription( 'Report the hostname of a slave server' );
-       }
-
-       public function execute() {
-               global $wgAllDBsAreLocalhost;
-               if ( $wgAllDBsAreLocalhost ) {
-                       $host = 'localhost';
-               } elseif ( $this->hasOption( 'group' ) ) {
-                       $db = $this->getDB( DB_SLAVE, $this->getOption( 'group' ) );
-                       $host = $db->getServer();
-               } else {
-                       $lb = wfGetLB();
-                       $i = $lb->getReaderIndex();
-                       $host = $lb->getServerName( $i );
-               }
-               $this->output( "$host\n" );
-       }
-}
-
-$maintClass = "GetSlaveServer";
-require_once RUN_MAINTENANCE_IF_MAIN;
+// B/C alias
+require_once ( __DIR__ . '/getReplicaServer.php' );
index c653a5f..ac700ef 100644 (file)
@@ -297,8 +297,8 @@ if ( $count > 0 ) {
 
                        if ( $doProtect ) {
                                # Protect the file
-                               echo "\nWaiting for slaves...\n";
-                               // Wait for slaves.
+                               echo "\nWaiting for replica DBs...\n";
+                               // Wait for replica DBs.
                                sleep( 2.0 ); # Why this sleep?
                                wfWaitForSlaves();
 
index 50a4018..6b06da7 100644 (file)
@@ -29,8 +29,8 @@ class InitEditCount extends Maintenance {
                parent::__construct();
                $this->addOption( 'quick', 'Force the update to be done in a single query' );
                $this->addOption( 'background', 'Force replication-friendly mode; may be inefficient but
-               avoids locking tables or lagging slaves with large updates;
-               calculates counts on a slave if possible.
+               avoids locking tables or lagging replica DBs with large updates;
+               calculates counts on a replica DB if possible.
 
 Background mode will be automatically used if multiple servers are listed
 in the load balancer, usually indicating a replication environment.' );
@@ -56,7 +56,7 @@ in the load balancer, usually indicating a replication environment.' );
                if ( $backgroundMode ) {
                        $this->output( "Using replication-friendly background mode...\n" );
 
-                       $dbr = $this->getDB( DB_SLAVE );
+                       $dbr = $this->getDB( DB_REPLICA );
                        $chunkSize = 100;
                        $lastUser = $dbr->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
 
index 5ec6e34..31e02c2 100644 (file)
 一地里      一地裏
 一年里      一年裏
 中文里      中文裏
+英文里      英文裏
+古文里      古文裏
+经文里      經文裏
+论文里      論文裏
+譯文里      譯文裏
+原文里      原文裏
+正文里      正文裏
+下文里      下文裏
+条文里      條文裏
+画里 畫裏
 事里 事裏
 井里 井裏
 作品里      作品裏
 会里 會裏
 村里的      村裏的
 村里有      村裏有
+区里的      區裏的
+区里有      區裏有
 森林里      森林裏
 棺材里      棺材裏
 树林里      樹林裏
index 59219ae..075deab 100644 (file)
 划着独木舟        划著獨木舟
 着眼于      著眼於
 桃金娘      桃金孃
+粘膜 黏膜
 缺省 預設
 以太网      乙太網
 光盘 光碟
 撒切尔      柴契爾
 戴卓爾      柴契爾
 摩根士丹利        摩根史坦利
-拉普兰      拉布蘭
 戴克里先   戴克里先
 戈爾巴喬夫        戈巴契夫
 戈尔巴乔夫        戈巴契夫
index 8fb4198..3062c1e 100644 (file)
 冬冬鼓      鼕鼕鼓
 苧麻 苧麻
 张柏芝      張栢芝
+杜琪峰      杜琪峯
index 463c126..dd38a30 100644 (file)
 皺彆
 一彆頭
 并州
+幽并
 併力
 ,並力
 ,并力討
 扁擬穀盜蟲
 不穀
 辟穀
-米穀
-田穀
 脫穀機
 年穀
 礱穀
 月球曆表
 伊爾汗曆表
 延曆
+萬曆
+永曆
+聖人曆
+羅馬曆
+羅馬歷史
+羅馬歷代
+曆數書
+曆局
+授時曆
+顓頊曆
 共和歷史
 厤物之意
 爰定祥厤
 磨麵
 莜麵
 雲吞麵
+拌麵
+乾拌麵
 冷面相
 糞穢衊面
 僕僕
 松山庄
 香山庄
 中庄子
+新庄子
 田庄英雄
 本庄
 庄司
 鬼谷子
 谷子敬
 洪谷子
-西米谷
-世田谷
 聖馬爾谷日
 澀谷區
 開山闢谷
 鬥敗
 鬥戰
 窩裡鬥
+亂鬥
 石樑
 木樑
 藏歷史
 裡面
 這裡
 中文裡
+英文裡
+古文裡
+經文裡
+論文裡
+譯文裡
+原文裡
+正文裡
+下文裡
+條文裡
+畫裡
 洞裡
 洞里薩
 界裡
 村裡的
 村裡有
 鎮裡
+區裡的
+區裡有
+實驗裡
+註裡
 裏白 #植物常用名
 烏蘇里 #分詞用
 首發
 涂醒哲
 涂善妮
 涂敏恆
+涂爾幹
 故云
 強制作用
 鬱南
 卜云吉
 黎吉雲
 代表
°´ç\84¡æ\80\9cå¥\88
+怜奈
 于冠華
 于雲鶴
 于忠肅集
 不太準
 非常準
 很準
-萬曆
-永曆
 囓蟲
 勳勞
 勳績
 煙臺
 太醜
 御製
-聖人曆
 電影後
 封為后
 皮托管
 白面包青天
 天神之后
-羅馬曆
-羅馬歷史
-羅馬歷代
-曆數書
-曆局
 你誇
 誇你
 誇我
 控制
 限制
 強制
+改制成
+考試制度
 體徵
 綜合徵
 价川
 琺瑯
 菜餚
 梁啓超
-改制成
 王添灯
 腌臢
 風颳
index 942f3f2..7557a42 100644 (file)
@@ -91,7 +91,7 @@ class PopulateFilearchiveSha1 extends LoggedUpdateMaintenance {
                                break;
                        }
 
-                       // print status and let slaves catch up
+                       // print status and let replica DBs catch up
                        $this->output( sprintf(
                                "id %d done (up to %d), %5.3f%%  \r", $lastId, $endId, $lastId / $endId * 100 ) );
                        wfWaitForSlaves();
index dcb9933..5e44faf 100644 (file)
@@ -73,7 +73,7 @@ class PopulateRevisionLength extends LoggedUpdateMaintenance {
         * @return int
         */
        protected function doLenUpdates( $table, $idCol, $prefix, $fields ) {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $dbw = $this->getDB( DB_MASTER );
                $start = $dbw->selectField( $table, "MIN($idCol)", false, __METHOD__ );
                $end = $dbw->selectField( $table, "MAX($idCol)", false, __METHOD__ );
index 70a26cb..615d1fe 100644 (file)
@@ -106,7 +106,7 @@ class PurgeChangedFiles extends Maintenance {
                }
 
                // Validate the timestamps
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $this->startTimestamp = $dbr->timestamp( $this->getOption( 'starttime' ) );
                $this->endTimestamp = $dbr->timestamp( $this->getOption( 'endtime' ) );
 
@@ -137,7 +137,7 @@ class PurgeChangedFiles extends Maintenance {
         */
        protected function purgeFromLogType( $type ) {
                $repo = RepoGroup::singleton()->getLocalRepo();
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                foreach ( self::$typeMappings[$type] as $logType => $logActions ) {
                        $this->verbose( "Scanning for {$logType}/" . implode( ',', $logActions ) . "\n" );
index 58a4640..b354399 100644 (file)
@@ -65,7 +65,7 @@ class PurgeChangedPages extends Maintenance {
                        }
                }
 
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $minTime = $dbr->timestamp( $this->getOption( 'starttime' ) );
                $maxTime = $dbr->timestamp( $this->getOption( 'endtime' ) );
 
index 21d1169..5ca7918 100644 (file)
@@ -86,7 +86,7 @@ class PurgeList extends Maintenance {
         * @param int|bool $namespace
         */
        private function purgeNamespace( $namespace = false ) {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $startId = 0;
                if ( $namespace === false ) {
                        $conds = [];
index 38556ed..649557e 100644 (file)
@@ -70,7 +70,7 @@ class RebuildFileCache extends Maintenance {
 
                $this->output( "Building content page file cache from page {$start}!\n" );
 
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $overwrite = $this->getOption( 'overwrite', false );
                $start = ( $start > 0 )
                        ? $start
index f67739b..3157186 100644 (file)
@@ -127,7 +127,7 @@ class ImageBuilder extends Maintenance {
                $this->init( $count, $table );
                $this->output( "Processing $table...\n" );
 
-               $result = $this->getDB( DB_SLAVE )->select( $table, '*', [], __METHOD__ );
+               $result = $this->getDB( DB_REPLICA )->select( $table, '*', [], __METHOD__ );
 
                foreach ( $result as $row ) {
                        $update = call_user_func( $callback, $row, null );
index d2ee6fc..95822ca 100644 (file)
@@ -41,7 +41,7 @@ class RebuildAll extends Maintenance {
 
        public function execute() {
                // Rebuild the text index
-               if ( $this->getDB( DB_SLAVE )->getType() != 'postgres' ) {
+               if ( $this->getDB( DB_REPLICA )->getType() != 'postgres' ) {
                        $this->output( "** Rebuilding fulltext search index (if you abort "
                                . "this will break searching; run this script again to fix):\n" );
                        $rebuildText = $this->runChild( 'RebuildTextIndex', 'rebuildtextindex.php' );
index ea5b6f9..e075501 100644 (file)
@@ -47,7 +47,7 @@ class RefreshFileHeaders extends Maintenance {
                $end = str_replace( ' ', '_', $this->getOption( 'end', '' ) ); // page on img_name
 
                $count = 0;
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                do {
                        $conds = [ "img_name > {$dbr->addQuotes( $start )}" ];
                        if ( strlen( $end ) ) {
index 95a49d6..24c8c11 100644 (file)
@@ -74,7 +74,7 @@ class RefreshLinks extends Maintenance {
                $end = null, $redirectsOnly = false, $oldRedirectsOnly = false
        ) {
                $reportingInterval = 100;
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                if ( $start === null ) {
                        $start = 1;
@@ -258,7 +258,7 @@ class RefreshLinks extends Maintenance {
        ) {
                wfWaitForSlaves();
                $this->output( "Deleting illegal entries from the links tables...\n" );
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                do {
                        // Find the start of the next chunk. This is based only
                        // on existent page_ids.
@@ -299,7 +299,7 @@ class RefreshLinks extends Maintenance {
         */
        private function dfnCheckInterval( $start = null, $end = null, $batchSize = 100 ) {
                $dbw = $this->getDB( DB_MASTER );
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                $linksTables = [ // table name => page_id field
                        'pagelinks' => 'pl_from',
index 0f67317..1034005 100644 (file)
@@ -23,7 +23,7 @@ class RemoveInvalidEmails extends Maintenance {
        }
        public function execute() {
                $this->commit = $this->hasOption( 'commit' );
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $dbw = $this->getDB( DB_MASTER );
                $lastId = 0;
                do {
index e3c99ed..ec8fcfe 100644 (file)
@@ -45,7 +45,7 @@ class RemoveUnusedAccounts extends Maintenance {
                # Do an initial scan for inactive accounts and report the result
                $this->output( "Checking for unused user accounts...\n" );
                $del = [];
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $res = $dbr->select( 'user', [ 'user_id', 'user_name', 'user_touched' ], '', __METHOD__ );
                if ( $this->hasOption( 'ignore-groups' ) ) {
                        $excludedGroups = explode( ',', $this->getOption( 'ignore-groups' ) );
@@ -107,7 +107,7 @@ class RemoveUnusedAccounts extends Maintenance {
         * @return bool
         */
        private function isInactiveAccount( $id, $master = false ) {
-               $dbo = $this->getDB( $master ? DB_MASTER : DB_SLAVE );
+               $dbo = $this->getDB( $master ? DB_MASTER : DB_REPLICA );
                $checks = [
                        'revision' => 'rev',
                        'archive' => 'ar',
index 131a569..481da98 100644 (file)
@@ -67,8 +67,9 @@ class ResetUserTokens extends Maintenance {
                        wfCountDown( 5 );
                }
 
+               // We list user by user_id from one of the replica DBs
                // We list user by user_id from one of the slave database
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                $where = [];
                if ( $this->nullsOnly ) {
index 10c107e..5ad7d4e 100644 (file)
@@ -95,7 +95,7 @@ class RollbackEdits extends Maintenance {
         * @return array
         */
        private function getRollbackTitles( $user ) {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $titles = [];
                $results = $dbr->select(
                        [ 'page', 'revision' ],
index 2feae02..a5e7a2f 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Run a database query in batches and wait for slaves. This is used on large
+ * Run a database query in batches and wait for replica DBs. This is used on large
  * wikis to prevent replication lag from going through the roof when executing
  * large write queries.
  *
@@ -26,7 +26,7 @@
 require_once __DIR__ . '/Maintenance.php';
 
 /**
- * Maintenance script to run a database query in batches and wait for slaves.
+ * Maintenance script to run a database query in batches and wait for replica DBs.
  *
  * @ingroup Maintenance
  */
@@ -34,7 +34,7 @@ class BatchedQueryRunner extends Maintenance {
        public function __construct() {
                parent::__construct();
                $this->addDescription(
-                       "Run a query repeatedly until it affects 0 rows, and wait for slaves in between.\n" .
+                       "Run a query repeatedly until it affects 0 rows, and wait for replica DBs in between.\n" .
                                "NOTE: You need to set a LIMIT clause yourself." );
        }
 
index c3c2391..2e011fe 100644 (file)
@@ -53,8 +53,6 @@ class RunJobs extends Maintenance {
        }
 
        public function execute() {
-               global $wgCommandLineMode;
-
                if ( $this->hasOption( 'procs' ) ) {
                        $procs = intval( $this->getOption( 'procs' ) );
                        if ( $procs < 1 || $procs > 1000 ) {
@@ -70,10 +68,6 @@ class RunJobs extends Maintenance {
                $outputJSON = ( $this->getOption( 'result' ) === 'json' );
                $wait = $this->hasOption( 'wait' );
 
-               // Enable DBO_TRX for atomicity; JobRunner manages transactions
-               // and works well in web server mode already (@TODO: this is a hack)
-               $wgCommandLineMode = false;
-
                $runner = new JobRunner( LoggerFactory::getInstance( 'runJobs' ) );
                if ( !$outputJSON ) {
                        $runner->setDebugHandler( [ $this, 'debugInternal' ] );
@@ -111,8 +105,6 @@ class RunJobs extends Maintenance {
 
                        sleep( 1 );
                }
-
-               $wgCommandLineMode = true;
        }
 
        /**
index 1d3e43a..5a15165 100644 (file)
@@ -52,8 +52,8 @@ class ShowSiteStats extends Maintenance {
                        'ss_images' => 'Number of images',
                ];
 
-               // Get cached stats from slave database
-               $dbr = $this->getDB( DB_SLAVE );
+               // Get cached stats from a replica DB
+               $dbr = $this->getDB( DB_REPLICA );
                $stats = $dbr->selectRow( 'site_stats', '*', '', __METHOD__ );
 
                // Get maximum size for each column
index 1aceace..e6a30a3 100644 (file)
@@ -34,10 +34,13 @@ class MwSql extends Maintenance {
                parent::__construct();
                $this->addDescription( 'Send SQL queries to a MediaWiki database. ' .
                        'Takes a file name containing SQL as argument or runs interactively.' );
-               $this->addOption( 'query', 'Run a single query instead of running interactively', false, true );
+               $this->addOption( 'query',
+                       'Run a single query instead of running interactively', false, true );
                $this->addOption( 'cluster', 'Use an external cluster by name', false, true );
-               $this->addOption( 'wikidb', 'The database wiki ID to use if not the current one', false, true );
-               $this->addOption( 'slave', 'Use a slave server (either "any" or by name)', false, true );
+               $this->addOption( 'wikidb',
+                       'The database wiki ID to use if not the current one', false, true );
+               $this->addOption( 'replicadb',
+                       'Replica DB server to use instead of the master DB (can be "any")', false, true );
        }
 
        public function execute() {
@@ -50,30 +53,28 @@ class MwSql extends Maintenance {
                        $lb = wfGetLB( $wiki );
                }
                // Figure out which server to use
-               if ( $this->hasOption( 'slave' ) ) {
-                       $server = $this->getOption( 'slave' );
-                       if ( $server === 'any' ) {
-                               $index = DB_SLAVE;
-                       } else {
-                               $index = null;
-                               $serverCount = $lb->getServerCount();
-                               for ( $i = 0; $i < $serverCount; ++$i ) {
-                                       if ( $lb->getServerName( $i ) === $server ) {
-                                               $index = $i;
-                                               break;
-                                       }
-                               }
-                               if ( $index === null ) {
-                                       $this->error( "No slave server configured with the name '$server'.", 1 );
+               $replicaDB = $this->getOption( 'replicadb', $this->getOption( 'slave', '' ) );
+               if ( $replicaDB === 'any' ) {
+                       $index = DB_REPLICA;
+               } elseif ( $replicaDB != '' ) {
+                       $index = null;
+                       $serverCount = $lb->getServerCount();
+                       for ( $i = 0; $i < $serverCount; ++$i ) {
+                               if ( $lb->getServerName( $i ) === $replicaDB ) {
+                                       $index = $i;
+                                       break;
                                }
                        }
+                       if ( $index === null ) {
+                               $this->error( "No replica DB server configured with the name '$server'.", 1 );
+                       }
                } else {
                        $index = DB_MASTER;
                }
                // Get a DB handle (with this wiki's DB selected) from the appropriate load balancer
                $db = $lb->getConnection( $index, [], $wiki );
-               if ( $this->hasOption( 'slave' ) && $db->getLBInfo( 'master' ) !== null ) {
-                       $this->error( "The server selected ({$db->getServer()}) is not a slave.", 1 );
+               if ( $replicaDB != '' && $db->getLBInfo( 'master' ) !== null ) {
+                       $this->error( "The server selected ({$db->getServer()}) is not a replica DB.", 1 );
                }
 
                if ( $this->hasArg( 0 ) ) {
index d69e5b9..bb9f4ea 100644 (file)
@@ -57,7 +57,7 @@ class CheckStorage {
        ];
 
        function check( $fix = false, $xml = '' ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                if ( $fix ) {
                        print "Checking, will fix errors if possible...\n";
                } else {
@@ -462,7 +462,7 @@ class CheckStorage {
                        return;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $dbw = wfGetDB( DB_MASTER );
                $dbr->ping();
                $dbw->ping();
@@ -507,7 +507,7 @@ class CheckStorage {
                }
 
                // Find text row again
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $oldId = $dbr->selectField( 'revision', 'rev_text_id', [ 'rev_id' => $id ], __METHOD__ );
                if ( !$oldId ) {
                        echo "Missing revision row for rev_id $id\n";
index ba924bf..289d22d 100644 (file)
@@ -237,7 +237,7 @@ class CompressOld extends Maintenance {
        ) {
                $loadStyle = self::LS_CHUNKED;
 
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $dbw = $this->getDB( DB_MASTER );
 
                # Set up external storage
index 39e06a3..437bfcd 100644 (file)
@@ -36,7 +36,7 @@ class DumpRev extends Maintenance {
        }
 
        public function execute() {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $row = $dbr->selectRow(
                        [ 'text', 'revision' ],
                        [ 'old_flags', 'old_text' ],
index 94335cf..b444f31 100644 (file)
@@ -42,7 +42,7 @@ class FixBug20757 extends Maintenance {
        }
 
        function execute() {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $dbw = $this->getDB( DB_MASTER );
 
                $dryRun = $this->getOption( 'dry-run' );
@@ -281,7 +281,7 @@ class FixBug20757 extends Maintenance {
                                unset( $this->mapCache[$key] );
                        }
 
-                       $dbr = $this->getDB( DB_SLAVE );
+                       $dbr = $this->getDB( DB_REPLICA );
                        $map = [];
                        $res = $dbr->select( 'revision',
                                [ 'rev_id', 'rev_text_id' ],
index 80dc7f9..e117992 100644 (file)
@@ -51,7 +51,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 function moveToExternal( $cluster, $maxID, $minID = 1 ) {
        $fname = 'moveToExternal';
        $dbw = wfGetDB( DB_MASTER );
-       $dbr = wfGetDB( DB_SLAVE );
+       $dbr = wfGetDB( DB_REPLICA );
 
        $count = $maxID - $minID + 1;
        $blockSize = 1000;
index d775830..d7d0b84 100644 (file)
@@ -39,11 +39,11 @@ class OrphanStats extends Maintenance {
        protected function &getDB( $cluster, $groups = [], $wiki = false ) {
                $lb = wfGetLBFactory()->getExternalLB( $cluster );
 
-               return $lb->getConnection( DB_SLAVE );
+               return $lb->getConnection( DB_REPLICA );
        }
 
        public function execute() {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                if ( !$dbr->tableExists( 'blob_orphans' ) ) {
                        $this->error( "blob_orphans doesn't seem to exist, need to run trackBlobs.php first", true );
                }
index 292a25d..a0efcb8 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 use MediaWiki\Logger\LegacyLogger;
+use MediaWiki\MediaWikiServices;
 
 $optionsWithArgs = RecompressTracked::getOptionsWithArgs();
 require __DIR__ . '/../commandLine.inc';
@@ -60,17 +61,17 @@ class RecompressTracked {
        public $numProcs = 1;
        public $numBatches = 0;
        public $pageBlobClass, $orphanBlobClass;
-       public $slavePipes, $slaveProcs, $prevSlaveId;
+       public $replicaPipes, $replicaProcs, $prevReplicaId;
        public $copyOnly = false;
        public $isChild = false;
-       public $slaveId = false;
+       public $replicaId = false;
        public $noCount = false;
        public $debugLog, $infoLog, $criticalLog;
        public $store;
 
        private static $optionsWithArgs = [
                'procs',
-               'slave-id',
+               'replica-id',
                'debug-log',
                'info-log',
                'critical-log'
@@ -81,7 +82,7 @@ class RecompressTracked {
                'procs' => 'numProcs',
                'copy-only' => 'copyOnly',
                'child' => 'isChild',
-               'slave-id' => 'slaveId',
+               'replica-id' => 'replicaId',
                'debug-log' => 'debugLog',
                'info-log' => 'infoLog',
                'critical-log' => 'criticalLog',
@@ -109,8 +110,8 @@ class RecompressTracked {
                $this->store = new ExternalStoreDB;
                if ( !$this->isChild ) {
                        $GLOBALS['wgDebugLogPrefix'] = "RCT M: ";
-               } elseif ( $this->slaveId !== false ) {
-                       $GLOBALS['wgDebugLogPrefix'] = "RCT {$this->slaveId}: ";
+               } elseif ( $this->replicaId !== false ) {
+                       $GLOBALS['wgDebugLogPrefix'] = "RCT {$this->replicaId}: ";
                }
                $this->pageBlobClass = function_exists( 'xdiff_string_bdiff' ) ?
                        'DiffHistoryBlob' : 'ConcatenatedGzipHistoryBlob';
@@ -140,21 +141,21 @@ class RecompressTracked {
 
        function logToFile( $msg, $file ) {
                $header = '[' . date( 'd\TH:i:s' ) . '] ' . wfHostname() . ' ' . posix_getpid();
-               if ( $this->slaveId !== false ) {
-                       $header .= "({$this->slaveId})";
+               if ( $this->replicaId !== false ) {
+                       $header .= "({$this->replicaId})";
                }
                $header .= ' ' . wfWikiID();
                LegacyLogger::emit( sprintf( "%-50s %s\n", $header, $msg ), $file );
        }
 
        /**
-        * Wait until the selected slave has caught up to the master.
-        * This allows us to use the slave for things that were committed in a
+        * Wait until the selected replica DB has caught up to the master.
+        * This allows us to use the replica DB for things that were committed in a
         * previous part of this batch process.
         */
        function syncDBs() {
                $dbw = wfGetDB( DB_MASTER );
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $pos = $dbw->getMasterPos();
                $dbr->masterPosWait( $pos, 100000 );
        }
@@ -179,10 +180,10 @@ class RecompressTracked {
                }
 
                $this->syncDBs();
-               $this->startSlaveProcs();
+               $this->startReplicaProcs();
                $this->doAllPages();
                $this->doAllOrphans();
-               $this->killSlaveProcs();
+               $this->killReplicaProcs();
        }
 
        /**
@@ -190,7 +191,7 @@ class RecompressTracked {
         * @return bool
         */
        function checkTrackingTable() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                if ( !$dbr->tableExists( 'blob_tracking' ) ) {
                        $this->critical( "Error: blob_tracking table does not exist" );
 
@@ -212,10 +213,10 @@ class RecompressTracked {
         * This necessary because text recompression is slow: loading, compressing and
         * writing are all slow.
         */
-       function startSlaveProcs() {
+       function startReplicaProcs() {
                $cmd = 'php ' . wfEscapeShellArg( __FILE__ );
                foreach ( self::$cmdLineOptionMap as $cmdOption => $classOption ) {
-                       if ( $cmdOption == 'slave-id' ) {
+                       if ( $cmdOption == 'replica-id' ) {
                                continue;
                        } elseif ( in_array( $cmdOption, self::$optionsWithArgs ) && isset( $this->$classOption ) ) {
                                $cmd .= " --$cmdOption " . wfEscapeShellArg( $this->$classOption );
@@ -227,7 +228,7 @@ class RecompressTracked {
                        ' --wiki ' . wfEscapeShellArg( wfWikiID() ) .
                        ' ' . call_user_func_array( 'wfEscapeShellArg', $this->destClusters );
 
-               $this->slavePipes = $this->slaveProcs = [];
+               $this->replicaPipes = $this->replicaProcs = [];
                for ( $i = 0; $i < $this->numProcs; $i++ ) {
                        $pipes = [];
                        $spec = [
@@ -236,28 +237,28 @@ class RecompressTracked {
                                [ 'file', 'php://stderr', 'w' ]
                        ];
                        MediaWiki\suppressWarnings();
-                       $proc = proc_open( "$cmd --slave-id $i", $spec, $pipes );
+                       $proc = proc_open( "$cmd --replica-id $i", $spec, $pipes );
                        MediaWiki\restoreWarnings();
                        if ( !$proc ) {
-                               $this->critical( "Error opening slave process: $cmd" );
+                               $this->critical( "Error opening replica DB process: $cmd" );
                                exit( 1 );
                        }
-                       $this->slaveProcs[$i] = $proc;
-                       $this->slavePipes[$i] = $pipes[0];
+                       $this->replicaProcs[$i] = $proc;
+                       $this->replicaPipes[$i] = $pipes[0];
                }
-               $this->prevSlaveId = -1;
+               $this->prevReplicaId = -1;
        }
 
        /**
         * Gracefully terminate the child processes
         */
-       function killSlaveProcs() {
-               $this->info( "Waiting for slave processes to finish..." );
+       function killReplicaProcs() {
+               $this->info( "Waiting for replica DB processes to finish..." );
                for ( $i = 0; $i < $this->numProcs; $i++ ) {
-                       $this->dispatchToSlave( $i, 'quit' );
+                       $this->dispatchToReplica( $i, 'quit' );
                }
                for ( $i = 0; $i < $this->numProcs; $i++ ) {
-                       $status = proc_close( $this->slaveProcs[$i] );
+                       $status = proc_close( $this->replicaProcs[$i] );
                        if ( $status ) {
                                $this->critical( "Warning: child #$i exited with status $status" );
                        }
@@ -266,22 +267,22 @@ class RecompressTracked {
        }
 
        /**
-        * Dispatch a command to the next available slave.
-        * This may block until a slave finishes its work and becomes available.
+        * Dispatch a command to the next available replica DB.
+        * This may block until a replica DB finishes its work and becomes available.
         */
        function dispatch( /*...*/ ) {
                $args = func_get_args();
-               $pipes = $this->slavePipes;
+               $pipes = $this->replicaPipes;
                $numPipes = stream_select( $x = [], $pipes, $y = [], 3600 );
                if ( !$numPipes ) {
-                       $this->critical( "Error waiting to write to slaves. Aborting" );
+                       $this->critical( "Error waiting to write to replica DBs. Aborting" );
                        exit( 1 );
                }
                for ( $i = 0; $i < $this->numProcs; $i++ ) {
-                       $slaveId = ( $i + $this->prevSlaveId + 1 ) % $this->numProcs;
-                       if ( isset( $pipes[$slaveId] ) ) {
-                               $this->prevSlaveId = $slaveId;
-                               $this->dispatchToSlave( $slaveId, $args );
+                       $replicaId = ( $i + $this->prevReplicaId + 1 ) % $this->numProcs;
+                       if ( isset( $pipes[$replicaId] ) ) {
+                               $this->prevReplicaId = $replicaId;
+                               $this->dispatchToReplica( $replicaId, $args );
 
                                return;
                        }
@@ -291,21 +292,21 @@ class RecompressTracked {
        }
 
        /**
-        * Dispatch a command to a specified slave
-        * @param int $slaveId
+        * Dispatch a command to a specified replica DB
+        * @param int $replicaId
         * @param array|string $args
         */
-       function dispatchToSlave( $slaveId, $args ) {
+       function dispatchToReplica( $replicaId, $args ) {
                $args = (array)$args;
                $cmd = implode( ' ', $args );
-               fwrite( $this->slavePipes[$slaveId], "$cmd\n" );
+               fwrite( $this->replicaPipes[$replicaId], "$cmd\n" );
        }
 
        /**
         * Move all tracked pages to the new clusters
         */
        function doAllPages() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $i = 0;
                $startId = 0;
                if ( $this->noCount ) {
@@ -366,7 +367,7 @@ class RecompressTracked {
                if ( $current == $end || $this->numBatches >= $this->reportingInterval ) {
                        $this->numBatches = 0;
                        $this->info( "$label: $current / $end" );
-                       wfWaitForSlaves();
+                       MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->waitForReplication();
                }
        }
 
@@ -374,7 +375,7 @@ class RecompressTracked {
         * Move all orphan text to the new clusters
         */
        function doAllOrphans() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $startId = 0;
                $i = 0;
                if ( $this->noCount ) {
@@ -464,7 +465,7 @@ class RecompressTracked {
                                case 'quit':
                                        return;
                        }
-                       wfWaitForSlaves();
+                       MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->waitForReplication();
                }
        }
 
@@ -480,7 +481,7 @@ class RecompressTracked {
                } else {
                        $titleText = '[deleted]';
                }
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Finish any incomplete transactions
                if ( !$this->copyOnly ) {
@@ -491,6 +492,7 @@ class RecompressTracked {
                $startId = 0;
                $trx = new CgzCopyTransaction( $this, $this->pageBlobClass );
 
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                while ( true ) {
                        $res = $dbr->select(
                                [ 'blob_tracking', 'text' ],
@@ -532,7 +534,7 @@ class RecompressTracked {
                                        $this->debug( "$titleText: committing blob with " . $trx->getSize() . " items" );
                                        $trx->commit();
                                        $trx = new CgzCopyTransaction( $this, $this->pageBlobClass );
-                                       wfWaitForSlaves();
+                                       $lbFactory->waitForReplication();
                                }
                        }
                }
@@ -590,7 +592,8 @@ class RecompressTracked {
         * @param array $conds
         */
        function finishIncompleteMoves( $conds ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
 
                $startId = 0;
                $conds = array_merge( $conds, [
@@ -615,7 +618,7 @@ class RecompressTracked {
                                $startId = $row->bt_text_id;
                                $this->moveTextRow( $row->bt_text_id, $row->bt_new_url );
                                if ( $row->bt_text_id % 10 == 0 ) {
-                                       wfWaitForSlaves();
+                                       $lbFactory->waitForReplication();
                                }
                        }
                }
@@ -659,7 +662,8 @@ class RecompressTracked {
 
                $trx = new CgzCopyTransaction( $this, $this->orphanBlobClass );
 
-               $res = wfGetDB( DB_SLAVE )->select(
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $res = wfGetDB( DB_REPLICA )->select(
                        [ 'text', 'blob_tracking' ],
                        [ 'old_id', 'old_text', 'old_flags' ],
                        [
@@ -682,7 +686,7 @@ class RecompressTracked {
                                $this->debug( "[orphan]: committing blob with " . $trx->getSize() . " rows" );
                                $trx->commit();
                                $trx = new CgzCopyTransaction( $this, $this->orphanBlobClass );
-                               wfWaitForSlaves();
+                               $lbFactory->waitForReplication();
                        }
                }
                $this->debug( "[orphan]: committing blob with " . $trx->getSize() . " rows" );
@@ -762,7 +766,7 @@ class CgzCopyTransaction {
 
                /* Check to see if the target text_ids have been moved already.
                 *
-                * We originally read from the slave, so this can happen when a single
+                * We originally read from the replica DB, so this can happen when a single
                 * text_id is shared between multiple pages. It's rare, but possible
                 * if a delete/move/undelete cycle splits up a null edit.
                 *
index 33a9f96..8ca8bb2 100644 (file)
@@ -37,7 +37,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 function resolveStubs() {
        $fname = 'resolveStubs';
 
-       $dbr = wfGetDB( DB_SLAVE );
+       $dbr = wfGetDB( DB_REPLICA );
        $maxID = $dbr->selectField( 'text', 'MAX(old_id)', false, $fname );
        $blockSize = 10000;
        $numBlocks = intval( $maxID / $blockSize ) + 1;
@@ -73,7 +73,7 @@ function resolveStub( $id, $stubText, $flags ) {
        $stub = unserialize( $stubText );
        $flags = explode( ',', $flags );
 
-       $dbr = wfGetDB( DB_SLAVE );
+       $dbr = wfGetDB( DB_REPLICA );
        $dbw = wfGetDB( DB_MASTER );
 
        if ( strtolower( get_class( $stub ) ) !== 'historyblobstub' ) {
index 04d2557..c23f508 100644 (file)
@@ -23,7 +23,7 @@ require_once __DIR__ . '/../Maintenance.php';
 
 class StorageTypeStats extends Maintenance {
        function execute() {
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
 
                $endId = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ );
                if ( !$endId ) {
index 2692a07..90d8d03 100644 (file)
@@ -47,7 +47,7 @@ if ( isset( $options['limit'] ) ) {
 }
 $type = isset( $options['type'] ) ? $options['type'] : 'ConcatenatedGzipHistoryBlob';
 
-$dbr = $this->getDB( DB_SLAVE );
+$dbr = $this->getDB( DB_REPLICA );
 $res = $dbr->select(
        [ 'page', 'revision', 'text' ],
        '*',
index 214f5b1..a2dc376 100644 (file)
@@ -67,7 +67,7 @@ class TrackBlobs {
 
        function checkIntegrity() {
                echo "Doing integrity check...\n";
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Scan for HistoryBlobStub objects in the text table (bug 20757)
 
@@ -117,7 +117,7 @@ class TrackBlobs {
 
        function getTextClause() {
                if ( !$this->textClause ) {
-                       $dbr = wfGetDB( DB_SLAVE );
+                       $dbr = wfGetDB( DB_REPLICA );
                        $this->textClause = '';
                        foreach ( $this->clusters as $cluster ) {
                                if ( $this->textClause != '' ) {
@@ -147,7 +147,7 @@ class TrackBlobs {
         */
        function trackRevisions() {
                $dbw = wfGetDB( DB_MASTER );
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $textClause = $this->getTextClause();
                $startId = 0;
@@ -219,9 +219,9 @@ class TrackBlobs {
         * archive table counts as orphan for our purposes.
         */
        function trackOrphanText() {
-               # Wait until the blob_tracking table is available in the slave
+               # Wait until the blob_tracking table is available in the replica DB
                $dbw = wfGetDB( DB_MASTER );
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $pos = $dbw->getMasterPos();
                $dbr->masterPosWait( $pos, 100000 );
 
@@ -317,7 +317,7 @@ class TrackBlobs {
                        echo "Searching for orphan blobs in $cluster...\n";
                        $lb = wfGetLBFactory()->getExternalLB( $cluster );
                        try {
-                               $extDB = $lb->getConnection( DB_SLAVE );
+                               $extDB = $lb->getConnection( DB_REPLICA );
                        } catch ( DBConnectionError $e ) {
                                if ( strpos( $e->error, 'Unknown database' ) !== false ) {
                                        echo "No database on $cluster\n";
index f47e13c..9d7cc0e 100644 (file)
@@ -7,7 +7,7 @@ require_once __DIR__ . '/Maintenance.php';
 class TidyUpBug37714 extends Maintenance {
        public function execute() {
                // Search for all log entries which are about changing the visability of other log entries.
-               $result = $this->getDB( DB_SLAVE )->select(
+               $result = $this->getDB( DB_REPLICA )->select(
                        'logging',
                        [ 'log_id', 'log_params' ],
                        [
@@ -21,7 +21,7 @@ class TidyUpBug37714 extends Maintenance {
 
                foreach ( $result as $row ) {
                        $ids = explode( ',', explode( "\n", $row->log_params )[0] );
-                       $result = $this->getDB( DB_SLAVE )->select( // Work out what log entries were changed here.
+                       $result = $this->getDB( DB_REPLICA )->select( // Work out what log entries were changed here.
                                'logging',
                                'log_type',
                                [ 'log_id' => $ids ],
index dff5aec..213195d 100644 (file)
@@ -46,7 +46,7 @@ class UpdateArticleCount extends Maintenance {
                if ( $this->hasOption( 'use-master' ) ) {
                        $dbr = $this->getDB( DB_MASTER );
                } else {
-                       $dbr = $this->getDB( DB_SLAVE, 'vslow' );
+                       $dbr = $this->getDB( DB_REPLICA, 'vslow' );
                }
                $counter = new SiteStatsInit( $dbr );
                $result = $counter->articles();
index 930f533..e754e3c 100644 (file)
@@ -34,7 +34,7 @@ require_once __DIR__ . '/Maintenance.php';
  */
 class UpdateCollation extends Maintenance {
        const BATCH_SIZE = 100; // Number of rows to process in one batch
-       const SYNC_INTERVAL = 5; // Wait for slaves after this many batches
+       const SYNC_INTERVAL = 5; // Wait for replica DBs after this many batches
 
        public $sizeHistogram = [];
 
@@ -70,7 +70,7 @@ TEXT
                global $wgCategoryCollation;
 
                $dbw = $this->getDB( DB_MASTER );
-               $dbr = $this->getDB( DB_SLAVE );
+               $dbr = $this->getDB( DB_REPLICA );
                $force = $this->getOption( 'force' );
                $dryRun = $this->getOption( 'dry-run' );
                $verboseStats = $this->getOption( 'verbose-stats' );
@@ -224,7 +224,7 @@ TEXT
                        $this->output( "$count done.\n" );
 
                        if ( !$dryRun && ++$batchCount % self::SYNC_INTERVAL == 0 ) {
-                               $this->output( "Waiting for slaves ... " );
+                               $this->output( "Waiting for replica DBs ... " );
                                wfWaitForSlaves();
                                $this->output( "done\n" );
                        }
index 8b24b90..5ea3828 100644 (file)
@@ -109,7 +109,7 @@ class UpdateSpecialPages extends Maintenance {
                                                } while ( !wfGetLB()->pingAll() );
                                                $this->output( "Reconnected\n\n" );
                                        }
-                                       # Wait for the slave to catch up
+                                       # Wait for the replica DB to catch up
                                        wfWaitForSlaves();
                                } else {
                                        $this->output( "cheap, skipped\n" );
@@ -153,7 +153,7 @@ class UpdateSpecialPages extends Maintenance {
                                        $this->output( $minutes . 'm ' );
                                }
                                $this->output( sprintf( "%.2fs\n", $seconds ) );
-                               # Wait for the slave to catch up
+                               # Wait for the replica DB to catch up
                                wfWaitForSlaves();
                        }
                }
index 4b0a817..c657c03 100644 (file)
@@ -140,8 +140,8 @@ class UserOptions {
                $ret = [];
                $defaultOptions = User::getDefaultOptions();
 
-               // We list user by user_id from one of the slave database
-               $dbr = wfGetDB( DB_SLAVE );
+               // We list user by user_id from one of the replica DBs
+               $dbr = wfGetDB( DB_REPLICA );
                $result = $dbr->select( 'user',
                        [ 'user_id' ],
                        [],
@@ -194,8 +194,8 @@ class UserOptions {
        private function CHANGER() {
                $this->warn();
 
-               // We list user by user_id from one of the slave database
-               $dbr = wfGetDB( DB_SLAVE );
+               // We list user by user_id from one of the replica DBs
+               $dbr = wfGetDB( DB_REPLICA );
                $result = $dbr->select( 'user',
                        [ 'user_id' ],
                        [],
index 5f45176..7055f36 100644 (file)
@@ -989,6 +989,16 @@ return [
                // must be loaded on the bottom
                'position' => 'bottom',
        ],
+       'mediawiki.diff.styles' => [
+               'position' => 'top',
+               'styles' => [
+                       'resources/src/mediawiki/mediawiki.diff.styles.css',
+                       'resources/src/mediawiki/mediawiki.diff.styles.print.css' => [
+                               'media' => 'print'
+                       ],
+               ],
+               'targets' => [ 'desktop', 'mobile' ],
+       ],
        'mediawiki.feedback' => [
                'scripts' => 'resources/src/mediawiki/mediawiki.feedback.js',
                'styles' => 'resources/src/mediawiki/mediawiki.feedback.css',
@@ -1146,7 +1156,7 @@ return [
        'mediawiki.notification' => [
                'styles' => [
                        'resources/src/mediawiki/mediawiki.notification.common.css',
-                       'resources/src/mediawiki/mediawiki.notification.hideForPrint.css'
+                       'resources/src/mediawiki/mediawiki.notification.print.css'
                                => [ 'media' => 'print' ],
                ],
                'skinStyles' => [
@@ -1484,7 +1494,7 @@ return [
                        'jquery.spinner',
                        'jquery.textSelection',
                        'mediawiki.api',
-                       'mediawiki.action.history.diff',
+                       'mediawiki.diff.styles',
                        'mediawiki.util',
                        'mediawiki.jqueryMsg',
                ],
@@ -1511,11 +1521,12 @@ return [
                'position' => 'top',
                'styles' => 'resources/src/mediawiki.action/mediawiki.action.history.styles.css',
        ],
+       // using this module is deprecated, for diff styles use mediawiki.diff.styles instead
        'mediawiki.action.history.diff' => [
                'position' => 'top',
                'styles' => [
-                       'resources/src/mediawiki.action/mediawiki.action.history.diff.css',
-                       'resources/src/mediawiki.action/mediawiki.action.history.diff.print.css' => [
+                       'resources/src/mediawiki/mediawiki.diff.styles.css',
+                       'resources/src/mediawiki/mediawiki.diff.styles.print.css' => [
                                'media' => 'print'
                        ],
                ],
@@ -1695,7 +1706,7 @@ return [
        ],
        'mediawiki.page.gallery.styles' => [
                'styles' => [
-                       'resources/src/mediawiki/page/gallery-print.css' => [ 'media' => 'print' ],
+                       'resources/src/mediawiki/page/gallery.print.css' => [ 'media' => 'print' ],
                        'resources/src/mediawiki/page/gallery.css',
                ],
                'position' => 'top',
diff --git a/resources/src/mediawiki.action/mediawiki.action.history.diff.css b/resources/src/mediawiki.action/mediawiki.action.history.diff.css
deleted file mode 100644 (file)
index 327c9c8..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-/*!
- * Diff rendering
- */
-table.diff {
-       border: none;
-       border-spacing: 4px;
-       margin: 0;
-       width: 100%;
-       /* Ensure that colums are of equal width */
-       table-layout: fixed;
-}
-
-table.diff td {
-       padding: 0.33em 0.5em;
-}
-
-table.diff td.diff-marker {
-       /* Compensate padding for increased font-size */
-       padding: 0.25em;
-}
-
-table.diff col.diff-marker {
-       width: 2%;
-}
-
-table.diff col.diff-content {
-       width: 48%;
-}
-
-table.diff td div {
-       /* Force-wrap very long lines such as URLs or page-widening char strings */
-       word-wrap: break-word;
-}
-
-td.diff-otitle,
-td.diff-ntitle {
-       text-align: center;
-}
-
-td.diff-lineno {
-       font-weight: bold;
-}
-
-td.diff-marker {
-       text-align: right;
-       font-weight: bold;
-       font-size: 1.25em;
-       line-height: 1.2;
-}
-
-td.diff-addedline,
-td.diff-deletedline,
-td.diff-context {
-       font-size: 88%;
-       line-height: 1.6;
-       vertical-align: top;
-       white-space: -moz-pre-wrap;
-       white-space: pre-wrap;
-       border-style: solid;
-       border-width: 1px 1px 1px 4px;
-       border-radius: 0.33em;
-}
-
-td.diff-addedline {
-       border-color: #a3d3ff;
-}
-
-td.diff-deletedline {
-       border-color: #ffe49c;
-}
-
-td.diff-context {
-       background: #f9f9f9;
-       border-color: #e6e6e6;
-       color: #333;
-}
-
-.diffchange {
-       font-weight: bold;
-       text-decoration: none;
-}
-
-td.diff-addedline .diffchange,
-td.diff-deletedline .diffchange {
-       border-radius: 0.33em;
-       padding: 0.25em 0;
-}
-
-td.diff-addedline .diffchange {
-       background: #d8ecff;
-}
-
-td.diff-deletedline .diffchange {
-       background: #feeec8;
-}
-
-/* Correct user & content directionality when viewing a diff */
-.diff-currentversion-title,
-.diff {
-       direction: ltr;
-       unicode-bidi: embed;
-}
-
-/* @noflip */ .diff-contentalign-right td {
-       direction: rtl;
-       unicode-bidi: embed;
-}
-
-/* @noflip */ .diff-contentalign-left td {
-       direction: ltr;
-       unicode-bidi: embed;
-}
-
-.diff-multi,
-.diff-otitle,
-.diff-ntitle,
-.diff-lineno {
-       direction: ltr !important;
-       unicode-bidi: embed;
-}
diff --git a/resources/src/mediawiki.action/mediawiki.action.history.diff.print.css b/resources/src/mediawiki.action/mediawiki.action.history.diff.print.css
deleted file mode 100644 (file)
index 76b5c9b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-/*!
- * Diff rendering
- */
-td.diff-context,
-td.diff-addedline .diffchange,
-td.diff-deletedline .diffchange {
-       background-color: transparent;
-}
-
-td.diff-addedline .diffchange {
-       text-decoration: underline;
-}
-
-td.diff-deletedline .diffchange {
-       text-decoration: line-through;
-}
index 899daa5..f51403f 100644 (file)
@@ -35,6 +35,9 @@
         * @constructor
         * @param {string|mw.Uri} url URL pointing to another wiki's `api.php` endpoint.
         * @param {Object} [options] See mw.Api.
+        * @param {Object} [options.anonymous=false] Perform all requests anonymously. Use this option if
+        *     the target wiki may otherwise not accept cross-origin requests, or if you don't need to
+        *     perform write actions or read restricted information and want to avoid the overhead.
         *
         * @author Bartosz Dziewoński
         * @author Jon Robson
                }
 
                this.apiUrl = String( url );
+               this.anonymous = options && options.anonymous;
 
                options = $.extend( /*deep=*/ true,
                        {
                                ajax: {
                                        url: this.apiUrl,
                                        xhrFields: {
-                                               withCredentials: true
+                                               withCredentials: this.anonymous ? false : true
                                        }
                                },
                                parameters: {
         * @return {string}
         */
        CoreForeignApi.prototype.getOrigin = function () {
-               var origin = location.protocol + '//' + location.hostname;
+               var origin;
+               if ( this.anonymous ) {
+                       return '*';
+               }
+               origin = location.protocol + '//' + location.hostname;
                if ( location.port ) {
                        origin += ':' + location.port;
                }
index eb2b3fc..cdc4dbf 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @class mw.Api.plugin.rollback
- * @since 1.27
+ * @since 1.28
  */
 ( function ( mw, $ ) {
 
index 31e4492..920835f 100644 (file)
                        if ( error.message ) {
                                return this.upload.getApi()
                                        .then( function ( api ) {
-                                               return api.loadMessagesIfMissing( [ error.message.key ] ).then( function () {
-                                                       if ( !mw.message( error.message.key ).exists() ) {
-                                                               return $.Deferred().reject();
-                                                       }
-                                                       return new OO.ui.Error(
-                                                               $( '<p>' ).msg( error.message.key, error.message.params || [] ),
-                                                               { recoverable: false }
-                                                       );
-                                               } );
+                                               // 'amenableparser' will expand templates and parser functions server-side.
+                                               // We still do the rest of wikitext parsing here (throught jqueryMsg).
+                                               return api.loadMessagesIfMissing( [ error.message.key ], { amenableparser: true } )
+                                                       .then( function () {
+                                                               if ( !mw.message( error.message.key ).exists() ) {
+                                                                       return $.Deferred().reject();
+                                                               }
+                                                               return new OO.ui.Error(
+                                                                       $( '<p>' ).msg( error.message.key, error.message.params || [] ),
+                                                                       { recoverable: false }
+                                                               );
+                                                       } );
                                        } )
                                        .then( null, function () {
                                                // We failed when loading the error message, or it doesn't actually exist, fall back
diff --git a/resources/src/mediawiki/mediawiki.diff.styles.css b/resources/src/mediawiki/mediawiki.diff.styles.css
new file mode 100644 (file)
index 0000000..327c9c8
--- /dev/null
@@ -0,0 +1,120 @@
+/*!
+ * Diff rendering
+ */
+table.diff {
+       border: none;
+       border-spacing: 4px;
+       margin: 0;
+       width: 100%;
+       /* Ensure that colums are of equal width */
+       table-layout: fixed;
+}
+
+table.diff td {
+       padding: 0.33em 0.5em;
+}
+
+table.diff td.diff-marker {
+       /* Compensate padding for increased font-size */
+       padding: 0.25em;
+}
+
+table.diff col.diff-marker {
+       width: 2%;
+}
+
+table.diff col.diff-content {
+       width: 48%;
+}
+
+table.diff td div {
+       /* Force-wrap very long lines such as URLs or page-widening char strings */
+       word-wrap: break-word;
+}
+
+td.diff-otitle,
+td.diff-ntitle {
+       text-align: center;
+}
+
+td.diff-lineno {
+       font-weight: bold;
+}
+
+td.diff-marker {
+       text-align: right;
+       font-weight: bold;
+       font-size: 1.25em;
+       line-height: 1.2;
+}
+
+td.diff-addedline,
+td.diff-deletedline,
+td.diff-context {
+       font-size: 88%;
+       line-height: 1.6;
+       vertical-align: top;
+       white-space: -moz-pre-wrap;
+       white-space: pre-wrap;
+       border-style: solid;
+       border-width: 1px 1px 1px 4px;
+       border-radius: 0.33em;
+}
+
+td.diff-addedline {
+       border-color: #a3d3ff;
+}
+
+td.diff-deletedline {
+       border-color: #ffe49c;
+}
+
+td.diff-context {
+       background: #f9f9f9;
+       border-color: #e6e6e6;
+       color: #333;
+}
+
+.diffchange {
+       font-weight: bold;
+       text-decoration: none;
+}
+
+td.diff-addedline .diffchange,
+td.diff-deletedline .diffchange {
+       border-radius: 0.33em;
+       padding: 0.25em 0;
+}
+
+td.diff-addedline .diffchange {
+       background: #d8ecff;
+}
+
+td.diff-deletedline .diffchange {
+       background: #feeec8;
+}
+
+/* Correct user & content directionality when viewing a diff */
+.diff-currentversion-title,
+.diff {
+       direction: ltr;
+       unicode-bidi: embed;
+}
+
+/* @noflip */ .diff-contentalign-right td {
+       direction: rtl;
+       unicode-bidi: embed;
+}
+
+/* @noflip */ .diff-contentalign-left td {
+       direction: ltr;
+       unicode-bidi: embed;
+}
+
+.diff-multi,
+.diff-otitle,
+.diff-ntitle,
+.diff-lineno {
+       direction: ltr !important;
+       unicode-bidi: embed;
+}
diff --git a/resources/src/mediawiki/mediawiki.diff.styles.print.css b/resources/src/mediawiki/mediawiki.diff.styles.print.css
new file mode 100644 (file)
index 0000000..76b5c9b
--- /dev/null
@@ -0,0 +1,16 @@
+/*!
+ * Diff rendering
+ */
+td.diff-context,
+td.diff-addedline .diffchange,
+td.diff-deletedline .diffchange {
+       background-color: transparent;
+}
+
+td.diff-addedline .diffchange {
+       text-decoration: underline;
+}
+
+td.diff-deletedline .diffchange {
+       text-decoration: line-through;
+}
index 78c674c..db5a4fb 100644 (file)
                                        // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use
                                        // XHR for a same domain request instead of <script>, which changes the request
                                        // headers (potentially missing a cache hit), and reduces caching in general
-                                       // since browsers cache XHR much less (if at all). And XHR means we retreive
+                                       // since browsers cache XHR much less (if at all). And XHR means we retrieve
                                        // text, so we'd need to $.globalEval, which then messes up line numbers.
                                        crossDomain: true,
                                        cache: true
                                }
                        }
 
+                       /**
+                        * Evaluate a batch of load.php responses retrieved from mw.loader.store.
+                        *
+                        * @private
+                        * @param {string[]} implementations Array containing pieces of JavaScript code in the
+                        *  form of calls to mw.loader#implement().
+                        * @param {Function} cb Callback in case of failure
+                        * @param {Error} cb.err
+                        */
+                       function batchEval( implementations, cb ) {
+                               if ( !implementations.length ) {
+                                       return;
+                               }
+                               mw.requestIdleCallback( function iterate( deadline ) {
+                                       while ( implementations[ 0 ] && deadline.timeRemaining() > 5 ) {
+                                               try {
+                                                       $.globalEval( implementations.shift() );
+                                               } catch ( err ) {
+                                                       cb( err );
+                                                       return;
+                                               }
+                                       }
+                                       if ( implementations[ 0 ] ) {
+                                               mw.requestIdleCallback( iterate );
+                                       }
+                               } );
+                       }
+
                        /* Public Members */
                        return {
                                /**
                                 * @protected
                                 */
                                work: function () {
-                                       var q, batch, concatSource, origBatch;
+                                       var q, batch, implementations, sourceModules;
 
                                        batch = [];
 
                                                }
                                        }
 
+                                       // Now that the queue has been processed into a batch, clear the queue.
+                                       // This MUST happen before we initiate any eval or network request. Otherwise,
+                                       // it is possible for a cached script to instantly trigger the same work queue
+                                       // again; all before we've cleared it causing each request to include modules
+                                       // which are already loaded.
+                                       queue = [];
+
+                                       if ( !batch.length ) {
+                                               return;
+                                       }
+
                                        mw.loader.store.init();
                                        if ( mw.loader.store.enabled ) {
-                                               concatSource = [];
-                                               origBatch = batch;
+                                               implementations = [];
+                                               sourceModules = [];
                                                batch = $.grep( batch, function ( module ) {
-                                                       var source = mw.loader.store.get( module );
-                                                       if ( source ) {
-                                                               concatSource.push( source );
+                                                       var implementation = mw.loader.store.get( module );
+                                                       if ( implementation ) {
+                                                               implementations.push( implementation );
+                                                               sourceModules.push( module );
                                                                return false;
                                                        }
                                                        return true;
                                                } );
-                                               try {
-                                                       $.globalEval( concatSource.join( ';' ) );
-                                               } catch ( err ) {
+                                               batchEval( implementations, function ( err ) {
                                                        // Not good, the cached mw.loader.implement calls failed! This should
                                                        // never happen, barring ResourceLoader bugs, browser bugs and PEBKACs.
                                                        // Depending on how corrupt the string is, it is likely that some
                                                        // modules' implement() succeeded while the ones after the error will
                                                        // never run and leave their modules in the 'loading' state forever.
-
                                                        // Since this is an error not caused by an individual module but by
                                                        // something that infected the implement call itself, don't take any
                                                        // risks and clear everything in this cache.
                                                        mw.loader.store.clear();
-                                                       // Re-add the ones still pending back to the batch and let the server
-                                                       // repopulate these modules to the cache.
-                                                       // This means that at most one module will be useless (the one that had
-                                                       // the error) instead of all of them.
                                                        mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } );
-                                                       origBatch = $.grep( origBatch, function ( module ) {
+
+                                                       // Re-add the failed ones that are still pending back to the batch
+                                                       var failed = $.grep( sourceModules, function ( module ) {
                                                                return registry[ module ].state === 'loading';
                                                        } );
-                                                       batch = batch.concat( origBatch );
-                                               }
+                                                       batchRequest( failed );
+                                               } );
                                        }
 
-                                       // Now that the queue has been processed into a batch, clear up the queue.
-                                       // This MUST happen before we initiate any network request. Else it's possible
-                                       // that a script will be locally cached, instantly load, and work the queue
-                                       // again; all before we've cleared it causing each request to include modules
-                                       // which are already loaded.
-                                       queue = [];
-
                                        batchRequest( batch );
                                },
 
diff --git a/resources/src/mediawiki/mediawiki.notification.hideForPrint.css b/resources/src/mediawiki/mediawiki.notification.hideForPrint.css
deleted file mode 100644 (file)
index 4f9162e..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-.mw-notification-area {
-       display: none;
-}
diff --git a/resources/src/mediawiki/mediawiki.notification.print.css b/resources/src/mediawiki/mediawiki.notification.print.css
new file mode 100644 (file)
index 0000000..4f9162e
--- /dev/null
@@ -0,0 +1,3 @@
+.mw-notification-area {
+       display: none;
+}
index 0fdd9aa..7df778f 100644 (file)
@@ -76,8 +76,8 @@
                                hexRnds[ i ] = byteToHex[ rnds[ i ] ];
                        }
 
-                       // Concatenation of two random integers with entrophy n and m
-                       // returns a string with entrophy n+m if those strings are independent
+                       // Concatenation of two random integers with entropy n and m
+                       // returns a string with entropy n+m if those strings are independent
                        return hexRnds.join( '' );
                },
 
diff --git a/resources/src/mediawiki/page/gallery-print.css b/resources/src/mediawiki/page/gallery-print.css
deleted file mode 100644 (file)
index 0c14865..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-li.gallerybox {
-       vertical-align: top;
-       display: inline-block;
-}
-
-ul.gallery, li.gallerybox {
-       zoom: 1;
-       *display: inline;
-}
-
-ul.gallery {
-       margin: 2px;
-       padding: 2px;
-       display: block;
-}
-
-li.gallerycaption {
-       font-weight: bold;
-       text-align: center;
-       display: block;
-       word-wrap: break-word;
-}
-
-li.gallerybox div.thumb {
-       text-align: center;
-       border: 1px solid #ccc;
-       margin: 2px;
-}
-
-div.gallerytext {
-       overflow: hidden;
-       font-size: 94%;
-       padding: 2px 4px;
-       word-wrap: break-word;
-}
index dabf475..474d541 100644 (file)
@@ -1,6 +1,6 @@
 /* Galleries */
 /* These display attributes look nonsensical, but are needed to support IE and FF2 */
-/* Don't forget to update gallery-print.css */
+/* Don't forget to update gallery.print.css */
 li.gallerybox {
        vertical-align: top;
        display: -moz-inline-box;
diff --git a/resources/src/mediawiki/page/gallery.print.css b/resources/src/mediawiki/page/gallery.print.css
new file mode 100644 (file)
index 0000000..0c14865
--- /dev/null
@@ -0,0 +1,35 @@
+li.gallerybox {
+       vertical-align: top;
+       display: inline-block;
+}
+
+ul.gallery, li.gallerybox {
+       zoom: 1;
+       *display: inline;
+}
+
+ul.gallery {
+       margin: 2px;
+       padding: 2px;
+       display: block;
+}
+
+li.gallerycaption {
+       font-weight: bold;
+       text-align: center;
+       display: block;
+       word-wrap: break-word;
+}
+
+li.gallerybox div.thumb {
+       text-align: center;
+       border: 1px solid #ccc;
+       margin: 2px;
+}
+
+div.gallerytext {
+       overflow: hidden;
+       font-size: 94%;
+       padding: 2px 4px;
+       word-wrap: break-word;
+}
index d973d07..83d14b3 100644 (file)
@@ -2,7 +2,7 @@
  * Enhance rollback links by using asynchronous API requests,
  * rather than navigating to an action page.
  *
- * @since 1.27
+ * @since 1.28
  * @author Timo Tijhof
  */
 ( function ( mw, $ ) {
@@ -15,7 +15,7 @@
                                page = mw.util.getParamValue( 'title', url ),
                                user = mw.util.getParamValue( 'from', url );
 
-                       if ( !page || !user ) {
+                       if ( !page || user === null ) {
                                // Let native browsing handle the link
                                return true;
                        }
index 5488280..4858703 100644 (file)
@@ -26,18 +26,6 @@ $testDir = __DIR__;
 
 $wgAutoloadClasses += [
 
-       # tests
-       'DbTestPreviewer' => "$testDir/testHelpers.inc",
-       'DbTestRecorder' => "$testDir/testHelpers.inc",
-       'DelayedParserTest' => "$testDir/testHelpers.inc",
-       'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
-       'TestFileIterator' => "$testDir/testHelpers.inc",
-       'TestFileDataProvider' => "$testDir/testHelpers.inc",
-       'TestRecorder' => "$testDir/testHelpers.inc",
-       'ITestRecorder' => "$testDir/testHelpers.inc",
-       'DjVuSupport' => "$testDir/testHelpers.inc",
-       'TidySupport' => "$testDir/testHelpers.inc",
-
        # tests/phpunit
        'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
        'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
@@ -142,11 +130,21 @@ $wgAutoloadClasses += [
        'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
 
        # tests/parser
-       'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php",
+       'DbTestPreviewer' => "$testDir/parser/DbTestPreviewer.php",
+       'DbTestRecorder' => "$testDir/parser/DbTestRecorder.php",
+       'DelayedParserTest' => "$testDir/parser/DelayedParserTest.php",
+       'DjVuSupport' => "$testDir/parser/DjVuSupport.php",
+       'ITestRecorder' => "$testDir/parser/ITestRecorder.php",
        'MediaWikiParserTest' => "$testDir/phpunit/includes/parser/MediaWikiParserTest.php",
-       'ParserTest' => "$testDir/parser/parserTest.inc",
-       'ParserTestResultNormalizer' => "$testDir/parser/parserTest.inc",
-       'ParserTestParserHook' => "$testDir/parser/parserTestsParserHook.php",
+       'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php",
+       'ParserTest' => "$testDir/parser/ParserTest.php",
+       'ParserTestParserHook' => "$testDir/parser/ParserTestParserHook.php",
+       'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
+       'ParserTestResultNormalizer' => "$testDir/parser/ParserTestResultNormalizer.php",
+       'TestFileDataProvider' => "$testDir/parser/TestFileDataProvider.php",
+       'TestFileIterator' => "$testDir/parser/TestFileIterator.php",
+       'TestRecorder' => "$testDir/parser/TestRecorder.php",
+       'TidySupport' => "$testDir/parser/TidySupport.php",
 
        # tests/phpunit/includes/site
        'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
index 8740e5d..3f451f1 100644 (file)
@@ -21,6 +21,7 @@ mw-vagrant-host: &default
 mw-vagrant-guest:
   user_factory: true
   mediawiki_url: http://127.0.0.1/wiki/
+  headless: 'true'
 
 beta:
   mediawiki_url: https://en.wikipedia.beta.wmflabs.org/wiki/
diff --git a/tests/parser/DbTestPreviewer.php b/tests/parser/DbTestPreviewer.php
new file mode 100644 (file)
index 0000000..2412254
--- /dev/null
@@ -0,0 +1,228 @@
+<?php
+/**
+ * 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 Testing
+ */
+
+class DbTestPreviewer extends TestRecorder {
+       protected $lb; // /< Database load balancer
+       protected $db; // /< Database connection to the main DB
+       protected $curRun; // /< run ID number for the current run
+       protected $prevRun; // /< run ID number for the previous run, if any
+       protected $results; // /< Result array
+
+       /**
+        * This should be called before the table prefix is changed
+        * @param TestRecorder $parent
+        */
+       function __construct( $parent ) {
+               parent::__construct( $parent );
+
+               $this->lb = wfGetLBFactory()->newMainLB();
+               // This connection will have the wiki's table prefix, not parsertest_
+               $this->db = $this->lb->getConnection( DB_MASTER );
+       }
+
+       /**
+        * Set up result recording; insert a record for the run with the date
+        * and all that fun stuff
+        */
+       function start() {
+               parent::start();
+
+               if ( !$this->db->tableExists( 'testrun', __METHOD__ )
+                       || !$this->db->tableExists( 'testitem', __METHOD__ )
+               ) {
+                       print "WARNING> `testrun` table not found in database.\n";
+                       $this->prevRun = false;
+               } else {
+                       // We'll make comparisons against the previous run later...
+                       $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' );
+               }
+
+               $this->results = [];
+       }
+
+       function getName( $test, $subtest ) {
+               if ( $subtest ) {
+                       return "$test subtest #$subtest";
+               } else {
+                       return $test;
+               }
+       }
+
+       function record( $test, $subtest, $result ) {
+               parent::record( $test, $subtest, $result );
+               $this->results[ $this->getName( $test, $subtest ) ] = $result;
+       }
+
+       function report() {
+               if ( $this->prevRun ) {
+                       // f = fail, p = pass, n = nonexistent
+                       // codes show before then after
+                       $table = [
+                               'fp' => 'previously failing test(s) now PASSING! :)',
+                               'pn' => 'previously PASSING test(s) removed o_O',
+                               'np' => 'new PASSING test(s) :)',
+
+                               'pf' => 'previously passing test(s) now FAILING! :(',
+                               'fn' => 'previously FAILING test(s) removed O_o',
+                               'nf' => 'new FAILING test(s) :(',
+                               'ff' => 'still FAILING test(s) :(',
+                       ];
+
+                       $prevResults = [];
+
+                       $res = $this->db->select( 'testitem', [ 'ti_name', 'ti_success' ],
+                               [ 'ti_run' => $this->prevRun ], __METHOD__ );
+
+                       foreach ( $res as $row ) {
+                               if ( !$this->parent->regex
+                                       || preg_match( "/{$this->parent->regex}/i", $row->ti_name )
+                               ) {
+                                       $prevResults[$row->ti_name] = $row->ti_success;
+                               }
+                       }
+
+                       $combined = array_keys( $this->results + $prevResults );
+
+                       # Determine breakdown by change type
+                       $breakdown = [];
+                       foreach ( $combined as $test ) {
+                               if ( !isset( $prevResults[$test] ) ) {
+                                       $before = 'n';
+                               } elseif ( $prevResults[$test] == 1 ) {
+                                       $before = 'p';
+                               } else /* if ( $prevResults[$test] == 0 )*/ {
+                                       $before = 'f';
+                               }
+
+                               if ( !isset( $this->results[$test] ) ) {
+                                       $after = 'n';
+                               } elseif ( $this->results[$test] == 1 ) {
+                                       $after = 'p';
+                               } else /*if ( $this->results[$test] == 0 ) */ {
+                                       $after = 'f';
+                               }
+
+                               $code = $before . $after;
+
+                               if ( isset( $table[$code] ) ) {
+                                       $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after );
+                               }
+                       }
+
+                       # Write out results
+                       foreach ( $table as $code => $label ) {
+                               if ( !empty( $breakdown[$code] ) ) {
+                                       $count = count( $breakdown[$code] );
+                                       printf( "\n%4d %s\n", $count, $label );
+
+                                       foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) {
+                                               print "      * $differing_test_name  [$statusInfo]\n";
+                                       }
+                               }
+                       }
+               } else {
+                       print "No previous test runs to compare against.\n";
+               }
+
+               print "\n";
+               parent::report();
+       }
+
+       /**
+        * Returns a string giving information about when a test last had a status change.
+        * Could help to track down when regressions were introduced, as distinct from tests
+        * which have never passed (which are more change requests than regressions).
+        * @param string $testname
+        * @param string $after
+        * @return string
+        */
+       private function getTestStatusInfo( $testname, $after ) {
+               // If we're looking at a test that has just been removed, then say when it first appeared.
+               if ( $after == 'n' ) {
+                       $changedRun = $this->db->selectField( 'testitem',
+                               'MIN(ti_run)',
+                               [ 'ti_name' => $testname ],
+                               __METHOD__ );
+                       $appear = $this->db->selectRow( 'testrun',
+                               [ 'tr_date', 'tr_mw_version' ],
+                               [ 'tr_id' => $changedRun ],
+                               __METHOD__ );
+
+                       return "First recorded appearance: "
+                               . date( "d-M-Y H:i:s", strtotime( $appear->tr_date ) )
+                               . ", " . $appear->tr_mw_version;
+               }
+
+               // Otherwise, this test has previous recorded results.
+               // See when this test last had a different result to what we're seeing now.
+               $conds = [
+                       'ti_name' => $testname,
+                       'ti_success' => ( $after == 'f' ? "1" : "0" ) ];
+
+               if ( $this->curRun ) {
+                       $conds[] = "ti_run != " . $this->db->addQuotes( $this->curRun );
+               }
+
+               $changedRun = $this->db->selectField( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ );
+
+               // If no record of ever having had a different result.
+               if ( is_null( $changedRun ) ) {
+                       if ( $after == "f" ) {
+                               return "Has never passed";
+                       } else {
+                               return "Has never failed";
+                       }
+               }
+
+               // Otherwise, we're looking at a test whose status has changed.
+               // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.)
+               // In this situation, give as much info as we can as to when it changed status.
+               $pre = $this->db->selectRow( 'testrun',
+                       [ 'tr_date', 'tr_mw_version' ],
+                       [ 'tr_id' => $changedRun ],
+                       __METHOD__ );
+               $post = $this->db->selectRow( 'testrun',
+                       [ 'tr_date', 'tr_mw_version' ],
+                       [ "tr_id > " . $this->db->addQuotes( $changedRun ) ],
+                       __METHOD__,
+                       [ "LIMIT" => 1, "ORDER BY" => 'tr_id' ]
+               );
+
+               if ( $post ) {
+                       $postDate = date( "d-M-Y H:i:s", strtotime( $post->tr_date ) ) . ", {$post->tr_mw_version}";
+               } else {
+                       $postDate = 'now';
+               }
+
+               return ( $after == "f" ? "Introduced" : "Fixed" ) . " between "
+                       . date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
+                       . " and $postDate";
+       }
+
+       /**
+        * Close the DB connection
+        */
+       function end() {
+               $this->lb->closeAll();
+               parent::end();
+       }
+}
+
diff --git a/tests/parser/DbTestRecorder.php b/tests/parser/DbTestRecorder.php
new file mode 100644 (file)
index 0000000..26aef97
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * 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 Testing
+ */
+
+class DbTestRecorder extends DbTestPreviewer {
+       public $version;
+
+       /**
+        * Set up result recording; insert a record for the run with the date
+        * and all that fun stuff
+        */
+       function start() {
+               $this->db->begin( __METHOD__ );
+
+               if ( !$this->db->tableExists( 'testrun' )
+                       || !$this->db->tableExists( 'testitem' )
+               ) {
+                       print "WARNING> `testrun` table not found in database. Trying to create table.\n";
+                       $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
+                       echo "OK, resuming.\n";
+               }
+
+               parent::start();
+
+               $this->db->insert( 'testrun',
+                       [
+                               'tr_date' => $this->db->timestamp(),
+                               'tr_mw_version' => $this->version,
+                               'tr_php_version' => PHP_VERSION,
+                               'tr_db_version' => $this->db->getServerVersion(),
+                               'tr_uname' => php_uname()
+                       ],
+                       __METHOD__ );
+               if ( $this->db->getType() === 'postgres' ) {
+                       $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' );
+               } else {
+                       $this->curRun = $this->db->insertId();
+               }
+       }
+
+       /**
+        * Record an individual test item's success or failure to the db
+        *
+        * @param string $test
+        * @param bool $result
+        */
+       function record( $test, $subtest, $result ) {
+               parent::record( $test, $subtest, $result );
+
+               $this->db->insert( 'testitem',
+                       [
+                               'ti_run' => $this->curRun,
+                               'ti_name' => $this->getName( $test, $subtest ),
+                               'ti_success' => $result ? 1 : 0,
+                       ],
+                       __METHOD__ );
+       }
+
+       /**
+        * Commit transaction and clean up for result recording
+        */
+       function end() {
+               $this->db->commit( __METHOD__ );
+               parent::end();
+       }
+}
+
diff --git a/tests/parser/DelayedParserTest.php b/tests/parser/DelayedParserTest.php
new file mode 100644 (file)
index 0000000..1c5c36b
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * 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 Testing
+ */
+
+/**
+ * A class to delay execution of a parser test hooks.
+ */
+class DelayedParserTest {
+
+       /** Initialized on construction */
+       private $hooks;
+       private $fnHooks;
+       private $transparentHooks;
+
+       public function __construct() {
+               $this->reset();
+       }
+
+       /**
+        * Init/reset or forgot about the current delayed test.
+        * Call to this will erase any hooks function that were pending.
+        */
+       public function reset() {
+               $this->hooks = [];
+               $this->fnHooks = [];
+               $this->transparentHooks = [];
+       }
+
+       /**
+        * Called whenever we actually want to run the hook.
+        * Should be the case if we found the parserTest is not disabled
+        * @param ParserTest|NewParserTest $parserTest
+        * @return bool
+        * @throws MWException
+        */
+       public function unleash( &$parserTest ) {
+               if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) {
+                       throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or "
+                               . "NewParserTest classes\n" );
+               }
+
+               # Trigger delayed hooks. Any failure will make us abort
+               foreach ( $this->hooks as $hook ) {
+                       $ret = $parserTest->requireHook( $hook );
+                       if ( !$ret ) {
+                               return false;
+                       }
+               }
+
+               # Trigger delayed function hooks. Any failure will make us abort
+               foreach ( $this->fnHooks as $fnHook ) {
+                       $ret = $parserTest->requireFunctionHook( $fnHook );
+                       if ( !$ret ) {
+                               return false;
+                       }
+               }
+
+               # Trigger delayed transparent hooks. Any failure will make us abort
+               foreach ( $this->transparentHooks as $hook ) {
+                       $ret = $parserTest->requireTransparentHook( $hook );
+                       if ( !$ret ) {
+                               return false;
+                       }
+               }
+
+               # Delayed execution was successful.
+               return true;
+       }
+
+       /**
+        * Similar to ParserTest object but does not run anything
+        * Use unleash() to really execute the hook
+        * @param string $hook
+        */
+       public function requireHook( $hook ) {
+               $this->hooks[] = $hook;
+       }
+
+       /**
+        * Similar to ParserTest object but does not run anything
+        * Use unleash() to really execute the hook function
+        * @param string $fnHook
+        */
+       public function requireFunctionHook( $fnHook ) {
+               $this->fnHooks[] = $fnHook;
+       }
+
+       /**
+        * Similar to ParserTest object but does not run anything
+        * Use unleash() to really execute the hook function
+        * @param string $hook
+        */
+       public function requireTransparentHook( $hook ) {
+               $this->transparentHooks[] = $hook;
+       }
+
+}
+
diff --git a/tests/parser/DjVuSupport.php b/tests/parser/DjVuSupport.php
new file mode 100644 (file)
index 0000000..4739be4
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * 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 Testing
+ */
+
+/**
+ * Initialize and detect the DjVu files support
+ */
+class DjVuSupport {
+
+       /**
+        * Initialises DjVu tools global with default values
+        */
+       public function __construct() {
+               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgFileExtensions, $wgDjvuTxt;
+
+               $wgDjvuRenderer = $wgDjvuRenderer ? $wgDjvuRenderer : '/usr/bin/ddjvu';
+               $wgDjvuDump = $wgDjvuDump ? $wgDjvuDump : '/usr/bin/djvudump';
+               $wgDjvuToXML = $wgDjvuToXML ? $wgDjvuToXML : '/usr/bin/djvutoxml';
+               $wgDjvuTxt = $wgDjvuTxt ? $wgDjvuTxt : '/usr/bin/djvutxt';
+
+               if ( !in_array( 'djvu', $wgFileExtensions ) ) {
+                       $wgFileExtensions[] = 'djvu';
+               }
+       }
+
+       /**
+        * Returns true if the DjVu tools are usable
+        *
+        * @return bool
+        */
+       public function isEnabled() {
+               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgDjvuTxt;
+
+               return is_executable( $wgDjvuRenderer )
+                       && is_executable( $wgDjvuDump )
+                       && is_executable( $wgDjvuToXML )
+                       && is_executable( $wgDjvuTxt );
+       }
+}
+
diff --git a/tests/parser/ITestRecorder.php b/tests/parser/ITestRecorder.php
new file mode 100644 (file)
index 0000000..5a78beb
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * 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 Testing
+ */
+
+/**
+ * Interface to record parser test results.
+ *
+ * The ITestRecorder is a very simple interface to record the result of
+ * MediaWiki parser tests. One should call start() before running the
+ * full parser tests and end() once all the tests have been finished.
+ * After each test, you should use record() to keep track of your tests
+ * results. Finally, report() is used to generate a summary of your
+ * test run, one could dump it to the console for human consumption or
+ * register the result in a database for tracking purposes.
+ *
+ * @since 1.22
+ */
+interface ITestRecorder {
+
+       /**
+        * Called at beginning of the parser test run
+        */
+       public function start();
+
+       /**
+        * Called after each test
+        * @param string $test
+        * @param integer $subtest
+        * @param bool $result
+        */
+       public function record( $test, $subtest, $result );
+
+       /**
+        * Called before finishing the test run
+        */
+       public function report();
+
+       /**
+        * Called at the end of the parser test run
+        */
+       public function end();
+
+}
+
diff --git a/tests/parser/ParserTest.php b/tests/parser/ParserTest.php
new file mode 100644 (file)
index 0000000..7b3746a
--- /dev/null
@@ -0,0 +1,1591 @@
+<?php
+/**
+ * Helper code for the MediaWiki parser test suite. Some code is duplicated
+ * in PHPUnit's NewParserTests.php, so you'll probably want to update both
+ * at the same time.
+ *
+ * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @todo Make this more independent of the configuration (and if possible the database)
+ * @todo document
+ * @file
+ * @ingroup Testing
+ */
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @ingroup Testing
+ */
+class ParserTest {
+       /**
+        * @var bool $color whereas output should be colorized
+        */
+       private $color;
+
+       /**
+        * @var bool $showOutput Show test output
+        */
+       private $showOutput;
+
+       /**
+        * @var bool $useTemporaryTables Use temporary tables for the temporary database
+        */
+       private $useTemporaryTables = true;
+
+       /**
+        * @var bool $databaseSetupDone True if the database has been set up
+        */
+       private $databaseSetupDone = false;
+
+       /**
+        * Our connection to the database
+        * @var DatabaseBase
+        */
+       private $db;
+
+       /**
+        * Database clone helper
+        * @var CloneDatabase
+        */
+       private $dbClone;
+
+       /**
+        * @var DjVuSupport
+        */
+       private $djVuSupport;
+
+       /**
+        * @var TidySupport
+        */
+       private $tidySupport;
+
+       /**
+        * @var ITestRecorder
+        */
+       private $recorder;
+
+       private $uploadDir = null;
+
+       public $regex = "";
+       private $savedGlobals = [];
+       private $useDwdiff = false;
+       private $markWhitespace = false;
+       private $normalizationFunctions = [];
+
+       /**
+        * Sets terminal colorization and diff/quick modes depending on OS and
+        * command-line options (--color and --quick).
+        * @param array $options
+        */
+       public function __construct( $options = [] ) {
+               # Only colorize output if stdout is a terminal.
+               $this->color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
+
+               if ( isset( $options['color'] ) ) {
+                       switch ( $options['color'] ) {
+                               case 'no':
+                                       $this->color = false;
+                                       break;
+                               case 'yes':
+                               default:
+                                       $this->color = true;
+                                       break;
+                       }
+               }
+
+               $this->term = $this->color
+                       ? new AnsiTermColorer()
+                       : new DummyTermColorer();
+
+               $this->showDiffs = !isset( $options['quick'] );
+               $this->showProgress = !isset( $options['quiet'] );
+               $this->showFailure = !(
+                       isset( $options['quiet'] )
+                               && ( isset( $options['record'] )
+                               || isset( $options['compare'] ) ) ); // redundant output
+
+               $this->showOutput = isset( $options['show-output'] );
+               $this->useDwdiff = isset( $options['dwdiff'] );
+               $this->markWhitespace = isset( $options['mark-ws'] );
+
+               if ( isset( $options['norm'] ) ) {
+                       foreach ( explode( ',', $options['norm'] ) as $func ) {
+                               if ( in_array( $func, [ 'removeTbody', 'trimWhitespace' ] ) ) {
+                                       $this->normalizationFunctions[] = $func;
+                               } else {
+                                       echo "Warning: unknown normalization option \"$func\"\n";
+                               }
+                       }
+               }
+
+               if ( isset( $options['filter'] ) ) {
+                       $options['regex'] = $options['filter'];
+               }
+
+               if ( isset( $options['regex'] ) ) {
+                       if ( isset( $options['record'] ) ) {
+                               echo "Warning: --record cannot be used with --regex, disabling --record\n";
+                               unset( $options['record'] );
+                       }
+                       $this->regex = $options['regex'];
+               } else {
+                       # Matches anything
+                       $this->regex = '';
+               }
+
+               $this->setupRecorder( $options );
+               $this->keepUploads = isset( $options['keep-uploads'] );
+
+               if ( $this->keepUploads ) {
+                       $this->uploadDir = wfTempDir() . '/mwParser-images';
+               } else {
+                       $this->uploadDir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
+               }
+
+               $this->runDisabled = isset( $options['run-disabled'] );
+               $this->runParsoid = isset( $options['run-parsoid'] );
+
+               $this->djVuSupport = new DjVuSupport();
+               $this->tidySupport = new TidySupport( isset( $options['use-tidy-config'] ) );
+               if ( !$this->tidySupport->isEnabled() ) {
+                       echo "Warning: tidy is not installed, skipping some tests\n";
+               }
+
+               $this->hooks = [];
+               $this->functionHooks = [];
+               $this->transparentHooks = [];
+               $this->setUp();
+       }
+
+       function setUp() {
+               global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
+                       $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
+                       $wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
+                       $wgExtraInterlanguageLinkPrefixes, $wgLocalInterwikis,
+                       $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, $wgResourceBasePath,
+                       $wgArticlePath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
+                       $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgLockManagers;
+
+               $wgScriptPath = '';
+               $wgScript = '/index.php';
+               $wgStylePath = '/skins';
+               $wgResourceBasePath = '';
+               $wgExtensionAssetsPath = '/extensions';
+               $wgArticlePath = '/wiki/$1';
+               $wgThumbnailScriptPath = false;
+               $wgLockManagers = [ [
+                       'name' => 'fsLockManager',
+                       'class' => 'FSLockManager',
+                       'lockDirectory' => $this->uploadDir . '/lockdir',
+               ], [
+                       'name' => 'nullLockManager',
+                       'class' => 'NullLockManager',
+               ] ];
+               $wgLocalFileRepo = [
+                       'class' => 'LocalRepo',
+                       'name' => 'local',
+                       'url' => 'http://example.com/images',
+                       'hashLevels' => 2,
+                       'transformVia404' => false,
+                       'backend' => new FSFileBackend( [
+                               'name' => 'local-backend',
+                               'wikiId' => wfWikiID(),
+                               'containerPaths' => [
+                                       'local-public' => $this->uploadDir . '/public',
+                                       'local-thumb' => $this->uploadDir . '/thumb',
+                                       'local-temp' => $this->uploadDir . '/temp',
+                                       'local-deleted' => $this->uploadDir . '/deleted',
+                               ]
+                       ] )
+               ];
+               $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
+               $wgNamespaceAliases['Image'] = NS_FILE;
+               $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
+               # add a namespace shadowing a interwiki link, to test
+               # proper precedence when resolving links. (bug 51680)
+               $wgExtraNamespaces[100] = 'MemoryAlpha';
+               $wgExtraNamespaces[101] = 'MemoryAlpha talk';
+
+               // XXX: tests won't run without this (for CACHE_DB)
+               if ( $wgMainCacheType === CACHE_DB ) {
+                       $wgMainCacheType = CACHE_NONE;
+               }
+               if ( $wgMessageCacheType === CACHE_DB ) {
+                       $wgMessageCacheType = CACHE_NONE;
+               }
+               if ( $wgParserCacheType === CACHE_DB ) {
+                       $wgParserCacheType = CACHE_NONE;
+               }
+
+               DeferredUpdates::clearPendingUpdates();
+               $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
+               $messageMemc = wfGetMessageCacheStorage();
+               $parserMemc = wfGetParserCacheStorage();
+
+               RequestContext::resetMain();
+               $context = new RequestContext;
+               $wgUser = new User;
+               $wgLang = $context->getLanguage();
+               $wgOut = $context->getOutput();
+               $wgRequest = $context->getRequest();
+               $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], [ $wgParserConf ] );
+
+               if ( $wgStyleDirectory === false ) {
+                       $wgStyleDirectory = "$IP/skins";
+               }
+
+               self::setupInterwikis();
+               $wgLocalInterwikis = [ 'local', 'mi' ];
+               // "extra language links"
+               // see https://gerrit.wikimedia.org/r/111390
+               array_push( $wgExtraInterlanguageLinkPrefixes, 'mul' );
+
+               // Reset namespace cache
+               MWNamespace::getCanonicalNamespaces( true );
+               Language::factory( 'en' )->resetNamespaces();
+       }
+
+       /**
+        * Insert hardcoded interwiki in the lookup table.
+        *
+        * This function insert a set of well known interwikis that are used in
+        * the parser tests. They can be considered has fixtures are injected in
+        * the interwiki cache by using the 'InterwikiLoadPrefix' hook.
+        * Since we are not interested in looking up interwikis in the database,
+        * the hook completely replace the existing mechanism (hook returns false).
+        */
+       public static function setupInterwikis() {
+               # Hack: insert a few Wikipedia in-project interwiki prefixes,
+               # for testing inter-language links
+               Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
+                       static $testInterwikis = [
+                               'local' => [
+                                       'iw_url' => 'http://doesnt.matter.org/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'wikipedia' => [
+                                       'iw_url' => 'http://en.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'meatball' => [
+                                       'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'memoryalpha' => [
+                                       'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 0 ],
+                               'zh' => [
+                                       'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'es' => [
+                                       'iw_url' => 'http://es.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'fr' => [
+                                       'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'ru' => [
+                                       'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'mi' => [
+                                       'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                               'mul' => [
+                                       'iw_url' => 'http://wikisource.org/wiki/$1',
+                                       'iw_api' => '',
+                                       'iw_wikiid' => '',
+                                       'iw_local' => 1 ],
+                       ];
+                       if ( array_key_exists( $prefix, $testInterwikis ) ) {
+                               $iwData = $testInterwikis[$prefix];
+                       }
+
+                       // We only want to rely on the above fixtures
+                       return false;
+               } );// hooks::register
+       }
+
+       /**
+        * Remove the hardcoded interwiki lookup table.
+        */
+       public static function tearDownInterwikis() {
+               Hooks::clear( 'InterwikiLoadPrefix' );
+       }
+
+       /**
+        * Reset the Title-related services that need resetting
+        * for each test
+        */
+       public static function resetTitleServices() {
+               $services = MediaWikiServices::getInstance();
+               $services->resetServiceForTesting( 'TitleFormatter' );
+               $services->resetServiceForTesting( 'TitleParser' );
+               $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
+               $services->resetServiceForTesting( 'LinkRenderer' );
+               $services->resetServiceForTesting( 'LinkRendererFactory' );
+       }
+
+       public function setupRecorder( $options ) {
+               if ( isset( $options['record'] ) ) {
+                       $this->recorder = new DbTestRecorder( $this );
+                       $this->recorder->version = isset( $options['setversion'] ) ?
+                               $options['setversion'] : SpecialVersion::getVersion();
+               } elseif ( isset( $options['compare'] ) ) {
+                       $this->recorder = new DbTestPreviewer( $this );
+               } else {
+                       $this->recorder = new TestRecorder( $this );
+               }
+       }
+
+       /**
+        * Remove last character if it is a newline
+        * @group utility
+        * @param string $s
+        * @return string
+        */
+       public static function chomp( $s ) {
+               if ( substr( $s, -1 ) === "\n" ) {
+                       return substr( $s, 0, -1 );
+               } else {
+                       return $s;
+               }
+       }
+
+       /**
+        * Run a series of tests listed in the given text files.
+        * Each test consists of a brief description, wikitext input,
+        * and the expected HTML output.
+        *
+        * Prints status updates on stdout and counts up the total
+        * number and percentage of passed tests.
+        *
+        * @param array $filenames Array of strings
+        * @return bool True if passed all tests, false if any tests failed.
+        */
+       public function runTestsFromFiles( $filenames ) {
+               $ok = false;
+
+               // be sure, ParserTest::addArticle has correct language set,
+               // so that system messages gets into the right language cache
+               $GLOBALS['wgLanguageCode'] = 'en';
+               $GLOBALS['wgContLang'] = Language::factory( 'en' );
+
+               $this->recorder->start();
+               try {
+                       $this->setupDatabase();
+                       $ok = true;
+
+                       foreach ( $filenames as $filename ) {
+                               echo "Running parser tests from: $filename\n";
+                               $tests = new TestFileIterator( $filename, $this );
+                               $ok = $this->runTests( $tests ) && $ok;
+                       }
+
+                       $this->teardownDatabase();
+                       $this->recorder->report();
+               } catch ( DBError $e ) {
+                       echo $e->getMessage();
+               }
+               $this->recorder->end();
+
+               return $ok;
+       }
+
+       function runTests( $tests ) {
+               $ok = true;
+
+               foreach ( $tests as $t ) {
+                       $result =
+                               $this->runTest( $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] );
+                       $ok = $ok && $result;
+                       $this->recorder->record( $t['test'], $t['subtest'], $result );
+               }
+
+               if ( $this->showProgress ) {
+                       print "\n";
+               }
+
+               return $ok;
+       }
+
+       /**
+        * Get a Parser object
+        *
+        * @param string $preprocessor
+        * @return Parser
+        */
+       function getParser( $preprocessor = null ) {
+               global $wgParserConf;
+
+               $class = $wgParserConf['class'];
+               $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
+
+               foreach ( $this->hooks as $tag => $callback ) {
+                       $parser->setHook( $tag, $callback );
+               }
+
+               foreach ( $this->functionHooks as $tag => $bits ) {
+                       list( $callback, $flags ) = $bits;
+                       $parser->setFunctionHook( $tag, $callback, $flags );
+               }
+
+               foreach ( $this->transparentHooks as $tag => $callback ) {
+                       $parser->setTransparentTagHook( $tag, $callback );
+               }
+
+               Hooks::run( 'ParserTestParser', [ &$parser ] );
+
+               return $parser;
+       }
+
+       /**
+        * Run a given wikitext input through a freshly-constructed wiki parser,
+        * and compare the output against the expected results.
+        * Prints status and explanatory messages to stdout.
+        *
+        * @param string $desc Test's description
+        * @param string $input Wikitext to try rendering
+        * @param string $result Result to output
+        * @param array $opts Test's options
+        * @param string $config Overrides for global variables, one per line
+        * @return bool
+        */
+       public function runTest( $desc, $input, $result, $opts, $config ) {
+               if ( $this->showProgress ) {
+                       $this->showTesting( $desc );
+               }
+
+               $opts = $this->parseOptions( $opts );
+               $context = $this->setupGlobals( $opts, $config );
+
+               $user = $context->getUser();
+               $options = ParserOptions::newFromContext( $context );
+
+               if ( isset( $opts['djvu'] ) ) {
+                       if ( !$this->djVuSupport->isEnabled() ) {
+                               return $this->showSkipped();
+                       }
+               }
+
+               if ( isset( $opts['tidy'] ) ) {
+                       if ( !$this->tidySupport->isEnabled() ) {
+                               return $this->showSkipped();
+                       } else {
+                               $options->setTidy( true );
+                       }
+               }
+
+               if ( isset( $opts['title'] ) ) {
+                       $titleText = $opts['title'];
+               } else {
+                       $titleText = 'Parser test';
+               }
+
+               ObjectCache::getMainWANInstance()->clearProcessCache();
+               $local = isset( $opts['local'] );
+               $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
+               $parser = $this->getParser( $preprocessor );
+               $title = Title::newFromText( $titleText );
+
+               if ( isset( $opts['pst'] ) ) {
+                       $out = $parser->preSaveTransform( $input, $title, $user, $options );
+               } elseif ( isset( $opts['msg'] ) ) {
+                       $out = $parser->transformMsg( $input, $options, $title );
+               } elseif ( isset( $opts['section'] ) ) {
+                       $section = $opts['section'];
+                       $out = $parser->getSection( $input, $section );
+               } elseif ( isset( $opts['replace'] ) ) {
+                       $section = $opts['replace'][0];
+                       $replace = $opts['replace'][1];
+                       $out = $parser->replaceSection( $input, $section, $replace );
+               } elseif ( isset( $opts['comment'] ) ) {
+                       $out = Linker::formatComment( $input, $title, $local );
+               } elseif ( isset( $opts['preload'] ) ) {
+                       $out = $parser->getPreloadText( $input, $title, $options );
+               } else {
+                       $output = $parser->parse( $input, $title, $options, true, true, 1337 );
+                       $output->setTOCEnabled( !isset( $opts['notoc'] ) );
+                       $out = $output->getText();
+                       if ( isset( $opts['tidy'] ) ) {
+                               $out = preg_replace( '/\s+$/', '', $out );
+                       }
+
+                       if ( isset( $opts['showtitle'] ) ) {
+                               if ( $output->getTitleText() ) {
+                                       $title = $output->getTitleText();
+                               }
+
+                               $out = "$title\n$out";
+                       }
+
+                       if ( isset( $opts['showindicators'] ) ) {
+                               $indicators = '';
+                               foreach ( $output->getIndicators() as $id => $content ) {
+                                       $indicators .= "$id=$content\n";
+                               }
+                               $out = $indicators . $out;
+                       }
+
+                       if ( isset( $opts['ill'] ) ) {
+                               $out = implode( ' ', $output->getLanguageLinks() );
+                       } elseif ( isset( $opts['cat'] ) ) {
+                               $outputPage = $context->getOutput();
+                               $outputPage->addCategoryLinks( $output->getCategories() );
+                               $cats = $outputPage->getCategoryLinks();
+
+                               if ( isset( $cats['normal'] ) ) {
+                                       $out = implode( ' ', $cats['normal'] );
+                               } else {
+                                       $out = '';
+                               }
+                       }
+               }
+
+               $this->teardownGlobals();
+
+               if ( count( $this->normalizationFunctions ) ) {
+                       $result = ParserTestResultNormalizer::normalize( $result, $this->normalizationFunctions );
+                       $out = ParserTestResultNormalizer::normalize( $out, $this->normalizationFunctions );
+               }
+
+               $testResult = new ParserTestResult( $desc );
+               $testResult->expected = $result;
+               $testResult->actual = $out;
+
+               return $this->showTestResult( $testResult );
+       }
+
+       /**
+        * Refactored in 1.22 to use ParserTestResult
+        * @param ParserTestResult $testResult
+        * @return bool
+        */
+       function showTestResult( ParserTestResult $testResult ) {
+               if ( $testResult->isSuccess() ) {
+                       $this->showSuccess( $testResult );
+                       return true;
+               } else {
+                       $this->showFailure( $testResult );
+                       return false;
+               }
+       }
+
+       /**
+        * Use a regex to find out the value of an option
+        * @param string $key Name of option val to retrieve
+        * @param array $opts Options array to look in
+        * @param mixed $default Default value returned if not found
+        * @return mixed
+        */
+       private static function getOptionValue( $key, $opts, $default ) {
+               $key = strtolower( $key );
+
+               if ( isset( $opts[$key] ) ) {
+                       return $opts[$key];
+               } else {
+                       return $default;
+               }
+       }
+
+       private function parseOptions( $instring ) {
+               $opts = [];
+               // foo
+               // foo=bar
+               // foo="bar baz"
+               // foo=[[bar baz]]
+               // foo=bar,"baz quux"
+               // foo={...json...}
+               $defs = '(?(DEFINE)
+                       (?<qstr>                                        # Quoted string
+                               "
+                               (?:[^\\\\"] | \\\\.)*
+                               "
+                       )
+                       (?<json>
+                               \{              # Open bracket
+                               (?:
+                                       [^"{}] |                                # Not a quoted string or object, or
+                                       (?&qstr) |                              # A quoted string, or
+                                       (?&json)                                # A json object (recursively)
+                               )*
+                               \}              # Close bracket
+                       )
+                       (?<value>
+                               (?:
+                                       (?&qstr)                        # Quoted val
+                               |
+                                       \[\[
+                                               [^]]*                   # Link target
+                                       \]\]
+                               |
+                                       [\w-]+                          # Plain word
+                               |
+                                       (?&json)                        # JSON object
+                               )
+                       )
+               )';
+               $regex = '/' . $defs . '\b
+                       (?<k>[\w-]+)                            # Key
+                       \b
+                       (?:\s*
+                               =                                               # First sub-value
+                               \s*
+                               (?<v>
+                                       (?&value)
+                                       (?:\s*
+                                               ,                               # Sub-vals 1..N
+                                               \s*
+                                               (?&value)
+                                       )*
+                               )
+                       )?
+                       /x';
+               $valueregex = '/' . $defs . '(?&value)/x';
+
+               if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
+                       foreach ( $matches as $bits ) {
+                               $key = strtolower( $bits['k'] );
+                               if ( !isset( $bits['v'] ) ) {
+                                       $opts[$key] = true;
+                               } else {
+                                       preg_match_all( $valueregex, $bits['v'], $vmatches );
+                                       $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
+                                       if ( count( $opts[$key] ) == 1 ) {
+                                               $opts[$key] = $opts[$key][0];
+                                       }
+                               }
+                       }
+               }
+               return $opts;
+       }
+
+       private function cleanupOption( $opt ) {
+               if ( substr( $opt, 0, 1 ) == '"' ) {
+                       return stripcslashes( substr( $opt, 1, -1 ) );
+               }
+
+               if ( substr( $opt, 0, 2 ) == '[[' ) {
+                       return substr( $opt, 2, -2 );
+               }
+
+               if ( substr( $opt, 0, 1 ) == '{' ) {
+                       return FormatJson::decode( $opt, true );
+               }
+               return $opt;
+       }
+
+       /**
+        * Set up the global variables for a consistent environment for each test.
+        * Ideally this should replace the global configuration entirely.
+        * @param string $opts
+        * @param string $config
+        * @return RequestContext
+        */
+       public function setupGlobals( $opts = '', $config = '' ) {
+               # Find out values for some special options.
+               $lang =
+                       self::getOptionValue( 'language', $opts, 'en' );
+               $variant =
+                       self::getOptionValue( 'variant', $opts, false );
+               $maxtoclevel =
+                       self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
+               $linkHolderBatchSize =
+                       self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
+
+               $settings = [
+                       'wgServer' => 'http://example.org',
+                       'wgServerName' => 'example.org',
+                       'wgScript' => '/index.php',
+                       'wgScriptPath' => '',
+                       'wgArticlePath' => '/wiki/$1',
+                       'wgActionPaths' => [],
+                       'wgLockManagers' => [ [
+                               'name' => 'fsLockManager',
+                               'class' => 'FSLockManager',
+                               'lockDirectory' => $this->uploadDir . '/lockdir',
+                       ], [
+                               'name' => 'nullLockManager',
+                               'class' => 'NullLockManager',
+                       ] ],
+                       'wgLocalFileRepo' => [
+                               'class' => 'LocalRepo',
+                               'name' => 'local',
+                               'url' => 'http://example.com/images',
+                               'hashLevels' => 2,
+                               'transformVia404' => false,
+                               'backend' => new FSFileBackend( [
+                                       'name' => 'local-backend',
+                                       'wikiId' => wfWikiID(),
+                                       'containerPaths' => [
+                                               'local-public' => $this->uploadDir,
+                                               'local-thumb' => $this->uploadDir . '/thumb',
+                                               'local-temp' => $this->uploadDir . '/temp',
+                                               'local-deleted' => $this->uploadDir . '/delete',
+                                       ]
+                               ] )
+                       ],
+                       'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
+                       'wgUploadNavigationUrl' => false,
+                       'wgStylePath' => '/skins',
+                       'wgSitename' => 'MediaWiki',
+                       'wgLanguageCode' => $lang,
+                       'wgDBprefix' => $this->db->getType() != 'oracle' ? 'parsertest_' : 'pt_',
+                       'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
+                       'wgLang' => null,
+                       'wgContLang' => null,
+                       'wgNamespacesWithSubpages' => [ 0 => isset( $opts['subpage'] ) ],
+                       'wgMaxTocLevel' => $maxtoclevel,
+                       'wgCapitalLinks' => true,
+                       'wgNoFollowLinks' => true,
+                       'wgNoFollowDomainExceptions' => [ 'no-nofollow.org' ],
+                       'wgThumbnailScriptPath' => false,
+                       'wgUseImageResize' => true,
+                       'wgSVGConverter' => 'null',
+                       'wgSVGConverters' => [ 'null' => 'echo "1">$output' ],
+                       'wgLocaltimezone' => 'UTC',
+                       'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
+                       'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
+                       'wgDefaultLanguageVariant' => $variant,
+                       'wgVariantArticlePath' => false,
+                       'wgGroupPermissions' => [ '*' => [
+                               'createaccount' => true,
+                               'read' => true,
+                               'edit' => true,
+                               'createpage' => true,
+                               'createtalk' => true,
+                       ] ],
+                       'wgNamespaceProtection' => [ NS_MEDIAWIKI => 'editinterface' ],
+                       'wgDefaultExternalStore' => [],
+                       'wgForeignFileRepos' => [],
+                       'wgLinkHolderBatchSize' => $linkHolderBatchSize,
+                       'wgExperimentalHtmlIds' => false,
+                       'wgExternalLinkTarget' => false,
+                       'wgHtml5' => true,
+                       'wgAdaptiveMessageCache' => true,
+                       'wgDisableLangConversion' => false,
+                       'wgDisableTitleConversion' => false,
+                       // Tidy options.
+                       'wgUseTidy' => false,
+                       'wgTidyConfig' => isset( $opts['tidy'] ) ? $this->tidySupport->getConfig() : null
+               ];
+
+               if ( $config ) {
+                       $configLines = explode( "\n", $config );
+
+                       foreach ( $configLines as $line ) {
+                               list( $var, $value ) = explode( '=', $line, 2 );
+
+                               $settings[$var] = eval( "return $value;" );
+                       }
+               }
+
+               $this->savedGlobals = [];
+
+               /** @since 1.20 */
+               Hooks::run( 'ParserTestGlobals', [ &$settings ] );
+
+               foreach ( $settings as $var => $val ) {
+                       if ( array_key_exists( $var, $GLOBALS ) ) {
+                               $this->savedGlobals[$var] = $GLOBALS[$var];
+                       }
+
+                       $GLOBALS[$var] = $val;
+               }
+
+               // Must be set before $context as user language defaults to $wgContLang
+               $GLOBALS['wgContLang'] = Language::factory( $lang );
+               $GLOBALS['wgMemc'] = new EmptyBagOStuff;
+
+               RequestContext::resetMain();
+               $context = RequestContext::getMain();
+               $GLOBALS['wgLang'] = $context->getLanguage();
+               $GLOBALS['wgOut'] = $context->getOutput();
+               $GLOBALS['wgUser'] = $context->getUser();
+
+               // We (re)set $wgThumbLimits to a single-element array above.
+               $context->getUser()->setOption( 'thumbsize', 0 );
+
+               global $wgHooks;
+
+               $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
+               $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
+
+               MagicWord::clearCache();
+               MWTidy::destroySingleton();
+               RepoGroup::destroySingleton();
+
+               self::resetTitleServices();
+
+               return $context;
+       }
+
+       /**
+        * List of temporary tables to create, without prefix.
+        * Some of these probably aren't necessary.
+        * @return array
+        */
+       private function listTables() {
+               $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
+                       'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
+                       'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
+                       'site_stats', 'ipblocks', 'image', 'oldimage',
+                       'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
+                       'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
+                       'archive', 'user_groups', 'page_props', 'category'
+               ];
+
+               if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
+                       array_push( $tables, 'searchindex' );
+               }
+
+               // Allow extensions to add to the list of tables to duplicate;
+               // may be necessary if they hook into page save or other code
+               // which will require them while running tests.
+               Hooks::run( 'ParserTestTables', [ &$tables ] );
+
+               return $tables;
+       }
+
+       /**
+        * Set up a temporary set of wiki tables to work with for the tests.
+        * Currently this will only be done once per run, and any changes to
+        * the db will be visible to later tests in the run.
+        */
+       public function setupDatabase() {
+               global $wgDBprefix;
+
+               if ( $this->databaseSetupDone ) {
+                       return;
+               }
+
+               $this->db = wfGetDB( DB_MASTER );
+               $dbType = $this->db->getType();
+
+               if ( $wgDBprefix === 'parsertest_' || ( $dbType == 'oracle' && $wgDBprefix === 'pt_' ) ) {
+                       throw new MWException( 'setupDatabase should be called before setupGlobals' );
+               }
+
+               $this->databaseSetupDone = true;
+
+               # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
+               # It seems to have been fixed since (r55079?), but regressed at some point before r85701.
+               # This works around it for now...
+               ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
+
+               # CREATE TEMPORARY TABLE breaks if there is more than one server
+               if ( wfGetLB()->getServerCount() != 1 ) {
+                       $this->useTemporaryTables = false;
+               }
+
+               $temporary = $this->useTemporaryTables || $dbType == 'postgres';
+               $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
+
+               $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
+               $this->dbClone->useTemporaryTables( $temporary );
+               $this->dbClone->cloneTableStructure();
+
+               if ( $dbType == 'oracle' ) {
+                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+                       # Insert 0 user to prevent FK violations
+
+                       # Anonymous user
+                       $this->db->insert( 'user', [
+                               'user_id' => 0,
+                               'user_name' => 'Anonymous' ] );
+               }
+
+               # Update certain things in site_stats
+               $this->db->insert( 'site_stats',
+                       [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ] );
+
+               # Reinitialise the LocalisationCache to match the database state
+               Language::getLocalisationCache()->unloadAll();
+
+               # Clear the message cache
+               MessageCache::singleton()->clear();
+
+               // Remember to update newParserTests.php after changing the below
+               // (and it uses a slightly different syntax just for teh lulz)
+               $this->setupUploadDir();
+               $user = User::createNew( 'WikiSysop' );
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
+               # note that the size/width/height/bits/etc of the file
+               # are actually set by inspecting the file itself; the arguments
+               # to recordUpload2 have no effect.  That said, we try to make things
+               # match up so it is less confusing to readers of the code & tests.
+               $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
+                       'size' => 7881,
+                       'width' => 1941,
+                       'height' => 220,
+                       'bits' => 8,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/jpeg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
+               # again, note that size/width/height below are ignored; see above.
+               $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
+                       'size' => 22589,
+                       'width' => 135,
+                       'height' => 135,
+                       'bits' => 8,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/png',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20130225203040' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
+               $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
+                               'size'        => 12345,
+                               'width'       => 240,
+                               'height'      => 180,
+                               'bits'        => 0,
+                               'media_type'  => MEDIATYPE_DRAWING,
+                               'mime'        => 'image/svg+xml',
+                               'metadata'    => serialize( [] ),
+                               'sha1'        => Wikimedia\base_convert( '', 16, 36, 31 ),
+                               'fileExists'  => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               # This image will be blacklisted in [[MediaWiki:Bad image list]]
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
+               $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
+                       'size' => 12345,
+                       'width' => 320,
+                       'height' => 240,
+                       'bits' => 24,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/jpeg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
+               $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
+                       'size' => 12345,
+                       'width' => 320,
+                       'height' => 240,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_VIDEO,
+                       'mime' => 'application/ogg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
+               $image->recordUpload2( '', 'An awesome hitsong', 'Will it play', [
+                       'size' => 12345,
+                       'width' => 0,
+                       'height' => 0,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_AUDIO,
+                       'mime' => 'application/ogg',
+                       'metadata' => serialize( [] ),
+                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123500' ), $user );
+
+               # A DjVu file
+               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
+               $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
+                       'size' => 3249,
+                       'width' => 2480,
+                       'height' => 3508,
+                       'bits' => 0,
+                       'media_type' => MEDIATYPE_BITMAP,
+                       'mime' => 'image/vnd.djvu',
+                       'metadata' => '<?xml version="1.0" ?>
+<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
+<DjVuXML>
+<HEAD></HEAD>
+<BODY><OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+</BODY>
+</DjVuXML>',
+                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+                       'fileExists' => true
+               ], $this->db->timestamp( '20010115123600' ), $user );
+       }
+
+       public function teardownDatabase() {
+               if ( !$this->databaseSetupDone ) {
+                       $this->teardownGlobals();
+                       return;
+               }
+               $this->teardownUploadDir( $this->uploadDir );
+
+               $this->dbClone->destroy();
+               $this->databaseSetupDone = false;
+
+               if ( $this->useTemporaryTables ) {
+                       if ( $this->db->getType() == 'sqlite' ) {
+                               # Under SQLite the searchindex table is virtual and need
+                               # to be explicitly destroyed. See bug 29912
+                               # See also MediaWikiTestCase::destroyDB()
+                               wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
+                               $this->db->query( "DROP TABLE `parsertest_searchindex`" );
+                       }
+                       # Don't need to do anything
+                       $this->teardownGlobals();
+                       return;
+               }
+
+               $tables = $this->listTables();
+
+               foreach ( $tables as $table ) {
+                       if ( $this->db->getType() == 'oracle' ) {
+                               $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
+                       } else {
+                               $this->db->query( "DROP TABLE `parsertest_$table`" );
+                       }
+               }
+
+               if ( $this->db->getType() == 'oracle' ) {
+                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+               }
+
+               $this->teardownGlobals();
+       }
+
+       /**
+        * Create a dummy uploads directory which will contain a couple
+        * of files in order to pass existence tests.
+        *
+        * @return string The directory
+        */
+       private function setupUploadDir() {
+               global $IP;
+
+               $dir = $this->uploadDir;
+               if ( $this->keepUploads && is_dir( $dir ) ) {
+                       return;
+               }
+
+               // wfDebug( "Creating upload directory $dir\n" );
+               if ( file_exists( $dir ) ) {
+                       wfDebug( "Already exists!\n" );
+                       return;
+               }
+
+               wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
+               copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
+               wfMkdirParents( $dir . '/e/ea', null, __METHOD__ );
+               copy( "$IP/tests/phpunit/data/parser/wiki.png", "$dir/e/ea/Thumb.png" );
+               wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
+               copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/0/09/Bad.jpg" );
+               wfMkdirParents( $dir . '/f/ff', null, __METHOD__ );
+               file_put_contents( "$dir/f/ff/Foobar.svg",
+                       '<?xml version="1.0" encoding="utf-8"?>' .
+                       '<svg xmlns="http://www.w3.org/2000/svg"' .
+                       ' version="1.1" width="240" height="180"/>' );
+               wfMkdirParents( $dir . '/5/5f', null, __METHOD__ );
+               copy( "$IP/tests/phpunit/data/parser/LoremIpsum.djvu", "$dir/5/5f/LoremIpsum.djvu" );
+               wfMkdirParents( $dir . '/0/00', null, __METHOD__ );
+               copy( "$IP/tests/phpunit/data/parser/320x240.ogv", "$dir/0/00/Video.ogv" );
+               wfMkdirParents( $dir . '/4/41', null, __METHOD__ );
+               copy( "$IP/tests/phpunit/data/media/say-test.ogg", "$dir/4/41/Audio.oga" );
+
+               return;
+       }
+
+       /**
+        * Restore default values and perform any necessary clean-up
+        * after each test runs.
+        */
+       public function teardownGlobals() {
+               RepoGroup::destroySingleton();
+               FileBackendGroup::destroySingleton();
+               LockManagerGroup::destroySingletons();
+               LinkCache::singleton()->clear();
+               MWTidy::destroySingleton();
+
+               foreach ( $this->savedGlobals as $var => $val ) {
+                       $GLOBALS[$var] = $val;
+               }
+       }
+
+       /**
+        * Remove the dummy uploads directory
+        * @param string $dir
+        */
+       private function teardownUploadDir( $dir ) {
+               if ( $this->keepUploads ) {
+                       return;
+               }
+
+               // delete the files first, then the dirs.
+               self::deleteFiles(
+                       [
+                               "$dir/3/3a/Foobar.jpg",
+                               "$dir/thumb/3/3a/Foobar.jpg/*.jpg",
+                               "$dir/e/ea/Thumb.png",
+                               "$dir/0/09/Bad.jpg",
+                               "$dir/5/5f/LoremIpsum.djvu",
+                               "$dir/thumb/5/5f/LoremIpsum.djvu/*-LoremIpsum.djvu.jpg",
+                               "$dir/f/ff/Foobar.svg",
+                               "$dir/thumb/f/ff/Foobar.svg/*-Foobar.svg.png",
+                               "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
+                               "$dir/0/00/Video.ogv",
+                               "$dir/thumb/0/00/Video.ogv/120px--Video.ogv.jpg",
+                               "$dir/thumb/0/00/Video.ogv/180px--Video.ogv.jpg",
+                               "$dir/thumb/0/00/Video.ogv/240px--Video.ogv.jpg",
+                               "$dir/thumb/0/00/Video.ogv/320px--Video.ogv.jpg",
+                               "$dir/thumb/0/00/Video.ogv/270px--Video.ogv.jpg",
+                               "$dir/thumb/0/00/Video.ogv/320px-seek=2-Video.ogv.jpg",
+                               "$dir/thumb/0/00/Video.ogv/320px-seek=3.3666666666667-Video.ogv.jpg",
+                               "$dir/4/41/Audio.oga",
+                       ]
+               );
+
+               self::deleteDirs(
+                       [
+                               "$dir/3/3a",
+                               "$dir/3",
+                               "$dir/thumb/3/3a/Foobar.jpg",
+                               "$dir/thumb/3/3a",
+                               "$dir/thumb/3",
+                               "$dir/e/ea",
+                               "$dir/e",
+                               "$dir/f/ff/",
+                               "$dir/f/",
+                               "$dir/thumb/f/ff/Foobar.svg",
+                               "$dir/thumb/f/ff/",
+                               "$dir/thumb/f/",
+                               "$dir/0/00/",
+                               "$dir/0/09/",
+                               "$dir/0/",
+                               "$dir/5/5f",
+                               "$dir/5",
+                               "$dir/thumb/0/00/Video.ogv",
+                               "$dir/thumb/0/00",
+                               "$dir/thumb/0",
+                               "$dir/thumb/5/5f/LoremIpsum.djvu",
+                               "$dir/thumb/5/5f",
+                               "$dir/thumb/5",
+                               "$dir/thumb",
+                               "$dir/4/41",
+                               "$dir/4",
+                               "$dir/math/f/a/5",
+                               "$dir/math/f/a",
+                               "$dir/math/f",
+                               "$dir/math",
+                               "$dir/lockdir",
+                               "$dir",
+                       ]
+               );
+       }
+
+       /**
+        * Delete the specified files, if they exist.
+        * @param array $files Full paths to files to delete.
+        */
+       private static function deleteFiles( $files ) {
+               foreach ( $files as $pattern ) {
+                       foreach ( glob( $pattern ) as $file ) {
+                               if ( file_exists( $file ) ) {
+                                       unlink( $file );
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Delete the specified directories, if they exist. Must be empty.
+        * @param array $dirs Full paths to directories to delete.
+        */
+       private static function deleteDirs( $dirs ) {
+               foreach ( $dirs as $dir ) {
+                       if ( is_dir( $dir ) ) {
+                               rmdir( $dir );
+                       }
+               }
+       }
+
+       /**
+        * "Running test $desc..."
+        * @param string $desc
+        */
+       protected function showTesting( $desc ) {
+               print "Running test $desc... ";
+       }
+
+       /**
+        * Print a happy success message.
+        *
+        * Refactored in 1.22 to use ParserTestResult
+        *
+        * @param ParserTestResult $testResult
+        * @return bool
+        */
+       protected function showSuccess( ParserTestResult $testResult ) {
+               if ( $this->showProgress ) {
+                       print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
+               }
+
+               return true;
+       }
+
+       /**
+        * Print a failure message and provide some explanatory output
+        * about what went wrong if so configured.
+        *
+        * Refactored in 1.22 to use ParserTestResult
+        *
+        * @param ParserTestResult $testResult
+        * @return bool
+        */
+       protected function showFailure( ParserTestResult $testResult ) {
+               if ( $this->showFailure ) {
+                       if ( !$this->showProgress ) {
+                               # In quiet mode we didn't show the 'Testing' message before the
+                               # test, in case it succeeded. Show it now:
+                               $this->showTesting( $testResult->description );
+                       }
+
+                       print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
+
+                       if ( $this->showOutput ) {
+                               print "--- Expected ---\n{$testResult->expected}\n";
+                               print "--- Actual ---\n{$testResult->actual}\n";
+                       }
+
+                       if ( $this->showDiffs ) {
+                               print $this->quickDiff( $testResult->expected, $testResult->actual );
+                               if ( !$this->wellFormed( $testResult->actual ) ) {
+                                       print "XML error: $this->mXmlError\n";
+                               }
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Print a skipped message.
+        *
+        * @return bool
+        */
+       protected function showSkipped() {
+               if ( $this->showProgress ) {
+                       print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
+               }
+
+               return true;
+       }
+
+       /**
+        * Run given strings through a diff and return the (colorized) output.
+        * Requires writable /tmp directory and a 'diff' command in the PATH.
+        *
+        * @param string $input
+        * @param string $output
+        * @param string $inFileTail Tailing for the input file name
+        * @param string $outFileTail Tailing for the output file name
+        * @return string
+        */
+       protected function quickDiff( $input, $output,
+               $inFileTail = 'expected', $outFileTail = 'actual'
+       ) {
+               if ( $this->markWhitespace ) {
+                       $pairs = [
+                               "\n" => '¶',
+                               ' ' => '·',
+                               "\t" => '→'
+                       ];
+                       $input = strtr( $input, $pairs );
+                       $output = strtr( $output, $pairs );
+               }
+
+               # Windows, or at least the fc utility, is retarded
+               $slash = wfIsWindows() ? '\\' : '/';
+               $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
+
+               $infile = "$prefix-$inFileTail";
+               $this->dumpToFile( $input, $infile );
+
+               $outfile = "$prefix-$outFileTail";
+               $this->dumpToFile( $output, $outfile );
+
+               $shellInfile = wfEscapeShellArg( $infile );
+               $shellOutfile = wfEscapeShellArg( $outfile );
+
+               global $wgDiff3;
+               // we assume that people with diff3 also have usual diff
+               if ( $this->useDwdiff ) {
+                       $shellCommand = 'dwdiff -Pc';
+               } else {
+                       $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
+               }
+
+               $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
+
+               unlink( $infile );
+               unlink( $outfile );
+
+               if ( $this->useDwdiff ) {
+                       return $diff;
+               } else {
+                       return $this->colorDiff( $diff );
+               }
+       }
+
+       /**
+        * Write the given string to a file, adding a final newline.
+        *
+        * @param string $data
+        * @param string $filename
+        */
+       private function dumpToFile( $data, $filename ) {
+               $file = fopen( $filename, "wt" );
+               fwrite( $file, $data . "\n" );
+               fclose( $file );
+       }
+
+       /**
+        * Colorize unified diff output if set for ANSI color output.
+        * Subtractions are colored blue, additions red.
+        *
+        * @param string $text
+        * @return string
+        */
+       protected function colorDiff( $text ) {
+               return preg_replace(
+                       [ '/^(-.*)$/m', '/^(\+.*)$/m' ],
+                       [ $this->term->color( 34 ) . '$1' . $this->term->reset(),
+                               $this->term->color( 31 ) . '$1' . $this->term->reset() ],
+                       $text );
+       }
+
+       /**
+        * Show "Reading tests from ..."
+        *
+        * @param string $path
+        */
+       public function showRunFile( $path ) {
+               print $this->term->color( 1 ) .
+                       "Reading tests from \"$path\"..." .
+                       $this->term->reset() .
+                       "\n";
+       }
+
+       /**
+        * Insert a temporary test article
+        * @param string $name The title, including any prefix
+        * @param string $text The article text
+        * @param int|string $line The input line number, for reporting errors
+        * @param bool|string $ignoreDuplicate Whether to silently ignore duplicate pages
+        * @throws Exception
+        * @throws MWException
+        */
+       public static function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
+               global $wgCapitalLinks;
+
+               $oldCapitalLinks = $wgCapitalLinks;
+               $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
+
+               $text = self::chomp( $text );
+               $name = self::chomp( $name );
+
+               $title = Title::newFromText( $name );
+
+               if ( is_null( $title ) ) {
+                       throw new MWException( "invalid title '$name' at line $line\n" );
+               }
+
+               $page = WikiPage::factory( $title );
+               $page->loadPageData( 'fromdbmaster' );
+
+               if ( $page->exists() ) {
+                       if ( $ignoreDuplicate == 'ignoreduplicate' ) {
+                               return;
+                       } else {
+                               throw new MWException( "duplicate article '$name' at line $line\n" );
+                       }
+               }
+
+               $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
+
+               $wgCapitalLinks = $oldCapitalLinks;
+       }
+
+       /**
+        * Steal a callback function from the primary parser, save it for
+        * application to our scary parser. If the hook is not installed,
+        * abort processing of this file.
+        *
+        * @param string $name
+        * @return bool True if tag hook is present
+        */
+       public function requireHook( $name ) {
+               global $wgParser;
+
+               $wgParser->firstCallInit(); // make sure hooks are loaded.
+
+               if ( isset( $wgParser->mTagHooks[$name] ) ) {
+                       $this->hooks[$name] = $wgParser->mTagHooks[$name];
+               } else {
+                       echo "   This test suite requires the '$name' hook extension, skipping.\n";
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * Steal a callback function from the primary parser, save it for
+        * application to our scary parser. If the hook is not installed,
+        * abort processing of this file.
+        *
+        * @param string $name
+        * @return bool True if function hook is present
+        */
+       public function requireFunctionHook( $name ) {
+               global $wgParser;
+
+               $wgParser->firstCallInit(); // make sure hooks are loaded.
+
+               if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
+                       $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name];
+               } else {
+                       echo "   This test suite requires the '$name' function hook extension, skipping.\n";
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * Steal a callback function from the primary parser, save it for
+        * application to our scary parser. If the hook is not installed,
+        * abort processing of this file.
+        *
+        * @param string $name
+        * @return bool True if function hook is present
+        */
+       public function requireTransparentHook( $name ) {
+               global $wgParser;
+
+               $wgParser->firstCallInit(); // make sure hooks are loaded.
+
+               if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
+                       $this->transparentHooks[$name] = $wgParser->mTransparentTagHooks[$name];
+               } else {
+                       echo "   This test suite requires the '$name' transparent hook extension, skipping.\n";
+                       return false;
+               }
+
+               return true;
+       }
+
+       private function wellFormed( $text ) {
+               $html =
+                       Sanitizer::hackDocType() .
+                               '<html>' .
+                               $text .
+                               '</html>';
+
+               $parser = xml_parser_create( "UTF-8" );
+
+               # case folding violates XML standard, turn it off
+               xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
+
+               if ( !xml_parse( $parser, $html, true ) ) {
+                       $err = xml_error_string( xml_get_error_code( $parser ) );
+                       $position = xml_get_current_byte_index( $parser );
+                       $fragment = $this->extractFragment( $html, $position );
+                       $this->mXmlError = "$err at byte $position:\n$fragment";
+                       xml_parser_free( $parser );
+
+                       return false;
+               }
+
+               xml_parser_free( $parser );
+
+               return true;
+       }
+
+       private function extractFragment( $text, $position ) {
+               $start = max( 0, $position - 10 );
+               $before = $position - $start;
+               $fragment = '...' .
+                       $this->term->color( 34 ) .
+                       substr( $text, $start, $before ) .
+                       $this->term->color( 0 ) .
+                       $this->term->color( 31 ) .
+                       $this->term->color( 1 ) .
+                       substr( $text, $position, 1 ) .
+                       $this->term->color( 0 ) .
+                       $this->term->color( 34 ) .
+                       substr( $text, $position + 1, 9 ) .
+                       $this->term->color( 0 ) .
+                       '...';
+               $display = str_replace( "\n", ' ', $fragment );
+               $caret = '   ' .
+                       str_repeat( ' ', $before ) .
+                       $this->term->color( 31 ) .
+                       '^' .
+                       $this->term->color( 0 );
+
+               return "$display\n$caret";
+       }
+
+       static function getFakeTimestamp( &$parser, &$ts ) {
+               $ts = 123; // parsed as '1970-01-01T00:02:03Z'
+               return true;
+       }
+}
diff --git a/tests/parser/ParserTestParserHook.php b/tests/parser/ParserTestParserHook.php
new file mode 100644 (file)
index 0000000..5bf50ea
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * A basic extension that's used by the parser tests to test whether input and
+ * arguments are passed to extensions properly.
+ *
+ * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason
+ *
+ * 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 Testing
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+class ParserTestParserHook {
+
+       static function setup( &$parser ) {
+               $parser->setHook( 'tag', [ __CLASS__, 'dumpHook' ] );
+               $parser->setHook( 'tåg', [ __CLASS__, 'dumpHook' ] );
+               $parser->setHook( 'statictag', [ __CLASS__, 'staticTagHook' ] );
+               return true;
+       }
+
+       static function dumpHook( $in, $argv ) {
+               return "<pre>\n" .
+                       var_export( $in, true ) . "\n" .
+                       var_export( $argv, true ) . "\n" .
+                       "</pre>";
+       }
+
+       static function staticTagHook( $in, $argv, $parser ) {
+               if ( !count( $argv ) ) {
+                       $parser->static_tag_buf = $in;
+                       return '';
+               } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
+                       && $argv['action'] === 'flush' && $in === null
+               ) {
+                       // Clear the buffer, we probably don't need to
+                       if ( isset( $parser->static_tag_buf ) ) {
+                               $tmp = $parser->static_tag_buf;
+                       } else {
+                               $tmp = '';
+                       }
+                       $parser->static_tag_buf = null;
+                       return $tmp;
+               } else { // wtf?
+                       return
+                               "\nCall this extension as <statictag>string</statictag> or as" .
+                               " <statictag action=flush/>, not in any other way.\n" .
+                               "text: " . var_export( $in, true ) . "\n" .
+                               "argv: " . var_export( $argv, true ) . "\n";
+               }
+       }
+}
diff --git a/tests/parser/ParserTestResultNormalizer.php b/tests/parser/ParserTestResultNormalizer.php
new file mode 100644 (file)
index 0000000..a15d09e
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ * @file
+ * @ingroup Testing
+ */
+
+class ParserTestResultNormalizer {
+       protected $doc, $xpath, $invalid;
+
+       public static function normalize( $text, $funcs ) {
+               $norm = new self( $text );
+               if ( $norm->invalid ) {
+                       return $text;
+               }
+               foreach ( $funcs as $func ) {
+                       $norm->$func();
+               }
+               return $norm->serialize();
+       }
+
+       protected function __construct( $text ) {
+               $this->doc = new DOMDocument( '1.0', 'utf-8' );
+
+               // Note: parsing a supposedly XHTML document with an XML parser is not
+               // guaranteed to give accurate results. For example, it may introduce
+               // differences in the number of line breaks in <pre> tags.
+
+               MediaWiki\suppressWarnings();
+               if ( !$this->doc->loadXML( '<html><body>' . $text . '</body></html>' ) ) {
+                       $this->invalid = true;
+               }
+               MediaWiki\restoreWarnings();
+               $this->xpath = new DOMXPath( $this->doc );
+               $this->body = $this->xpath->query( '//body' )->item( 0 );
+       }
+
+       protected function removeTbody() {
+               foreach ( $this->xpath->query( '//tbody' ) as $tbody ) {
+                       while ( $tbody->firstChild ) {
+                               $child = $tbody->firstChild;
+                               $tbody->removeChild( $child );
+                               $tbody->parentNode->insertBefore( $child, $tbody );
+                       }
+                       $tbody->parentNode->removeChild( $tbody );
+               }
+       }
+
+       /**
+        * The point of this function is to produce a normalized DOM in which
+        * Tidy's output matches the output of html5depurate. Tidy both trims
+        * and pretty-prints, so this requires fairly aggressive treatment.
+        *
+        * In particular, note that Tidy converts <pre>x</pre> to <pre>\nx\n</pre>,
+        * which theoretically affects display since the second line break is not
+        * ignored by compliant HTML parsers.
+        *
+        * This function also removes empty elements, as does Tidy.
+        */
+       protected function trimWhitespace() {
+               foreach ( $this->xpath->query( '//text()' ) as $child ) {
+                       if ( strtolower( $child->parentNode->nodeName ) === 'pre' ) {
+                               // Just trim one line break from the start and end
+                               if ( substr_compare( $child->data, "\n", 0 ) === 0 ) {
+                                       $child->data = substr( $child->data, 1 );
+                               }
+                               if ( substr_compare( $child->data, "\n", -1 ) === 0 ) {
+                                       $child->data = substr( $child->data, 0, -1 );
+                               }
+                       } else {
+                               // Trim all whitespace
+                               $child->data = trim( $child->data );
+                       }
+                       if ( $child->data === '' ) {
+                               $child->parentNode->removeChild( $child );
+                       }
+               }
+       }
+
+       /**
+        * Serialize the XML DOM for comparison purposes. This does not generate HTML.
+        */
+       protected function serialize() {
+               return strtr( $this->doc->saveXML( $this->body ),
+                       [ '<body>' => '', '</body>' => '' ] );
+       }
+}
+
diff --git a/tests/parser/TestFileDataProvider.php b/tests/parser/TestFileDataProvider.php
new file mode 100644 (file)
index 0000000..00b1f3f
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * 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 Testing
+ */
+
+/**
+ * An iterator for use as a phpunit data provider. Provides the test arguments
+ * in the order expected by NewParserTest::testParserTest().
+ */
+class TestFileDataProvider extends TestFileIterator {
+       function current() {
+               $test = parent::current();
+               if ( $test ) {
+                       return [
+                               $test['test'],
+                               $test['input'],
+                               $test['result'],
+                               $test['options'],
+                               $test['config'],
+                       ];
+               } else {
+                       return $test;
+               }
+       }
+}
+
diff --git a/tests/parser/TestFileIterator.php b/tests/parser/TestFileIterator.php
new file mode 100644 (file)
index 0000000..731d35c
--- /dev/null
@@ -0,0 +1,324 @@
+<?php
+/**
+ * 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 Testing
+ */
+
+class TestFileIterator implements Iterator {
+       private $file;
+       private $fh;
+       /**
+        * @var ParserTest|MediaWikiParserTest An instance of ParserTest (parserTests.php)
+        *  or MediaWikiParserTest (phpunit)
+        */
+       private $parserTest;
+       private $index = 0;
+       private $test;
+       private $section = null;
+       /** String|null: current test section being analyzed */
+       private $sectionData = [];
+       private $lineNum;
+       private $eof;
+       # Create a fake parser tests which never run anything unless
+       # asked to do so. This will avoid running hooks for a disabled test
+       private $delayedParserTest;
+       private $nextSubTest = 0;
+
+       function __construct( $file, $parserTest ) {
+               $this->file = $file;
+               $this->fh = fopen( $this->file, "rt" );
+
+               if ( !$this->fh ) {
+                       throw new MWException( "Couldn't open file '$file'\n" );
+               }
+
+               $this->parserTest = $parserTest;
+               $this->delayedParserTest = new DelayedParserTest();
+
+               $this->lineNum = $this->index = 0;
+       }
+
+       function rewind() {
+               if ( fseek( $this->fh, 0 ) ) {
+                       throw new MWException( "Couldn't fseek to the start of '$this->file'\n" );
+               }
+
+               $this->index = -1;
+               $this->lineNum = 0;
+               $this->eof = false;
+               $this->next();
+
+               return true;
+       }
+
+       function current() {
+               return $this->test;
+       }
+
+       function key() {
+               return $this->index;
+       }
+
+       function next() {
+               if ( $this->readNextTest() ) {
+                       $this->index++;
+                       return true;
+               } else {
+                       $this->eof = true;
+               }
+       }
+
+       function valid() {
+               return $this->eof != true;
+       }
+
+       function setupCurrentTest() {
+               // "input" and "result" are old section names allowed
+               // for backwards-compatibility.
+               $input = $this->checkSection( [ 'wikitext', 'input' ], false );
+               $result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
+               // some tests have "with tidy" and "without tidy" variants
+               $tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
+               if ( $tidy != false ) {
+                       if ( $this->nextSubTest == 0 ) {
+                               if ( $result != false ) {
+                                       $this->nextSubTest = 1; // rerun non-tidy variant later
+                               }
+                               $result = $tidy;
+                       } else {
+                               $this->nextSubTest = 0; // go on to next test after this
+                               $tidy = false;
+                       }
+               }
+
+               if ( !isset( $this->sectionData['options'] ) ) {
+                       $this->sectionData['options'] = '';
+               }
+
+               if ( !isset( $this->sectionData['config'] ) ) {
+                       $this->sectionData['config'] = '';
+               }
+
+               $isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
+                       !$this->parserTest->runDisabled;
+               $isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
+                       $result == 'html' &&
+                       !$this->parserTest->runParsoid;
+               $isFiltered = !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] );
+               if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
+                       # disabled test
+                       return false;
+               }
+
+               # We are really going to run the test, run pending hooks and hooks function
+               wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" );
+               $hooksResult = $this->delayedParserTest->unleash( $this->parserTest );
+               if ( !$hooksResult ) {
+                       # Some hook reported an issue. Abort.
+                       throw new MWException( "Problem running requested parser hook from the test file" );
+               }
+
+               $this->test = [
+                       'test' => ParserTest::chomp( $this->sectionData['test'] ),
+                       'subtest' => $this->nextSubTest,
+                       'input' => ParserTest::chomp( $this->sectionData[$input] ),
+                       'result' => ParserTest::chomp( $this->sectionData[$result] ),
+                       'options' => ParserTest::chomp( $this->sectionData['options'] ),
+                       'config' => ParserTest::chomp( $this->sectionData['config'] ),
+               ];
+               if ( $tidy != false ) {
+                       $this->test['options'] .= " tidy";
+               }
+               return true;
+       }
+
+       function readNextTest() {
+               # Run additional subtests of previous test
+               while ( $this->nextSubTest > 0 ) {
+                       if ( $this->setupCurrentTest() ) {
+                               return true;
+                       }
+               }
+
+               $this->clearSection();
+               # Reset hooks for the delayed test object
+               $this->delayedParserTest->reset();
+
+               while ( false !== ( $line = fgets( $this->fh ) ) ) {
+                       $this->lineNum++;
+                       $matches = [];
+
+                       if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
+                               $this->section = strtolower( $matches[1] );
+
+                               if ( $this->section == 'endarticle' ) {
+                                       $this->checkSection( 'text' );
+                                       $this->checkSection( 'article' );
+
+                                       $this->parserTest->addArticle(
+                                               ParserTest::chomp( $this->sectionData['article'] ),
+                                               $this->sectionData['text'], $this->lineNum );
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'endhooks' ) {
+                                       $this->checkSection( 'hooks' );
+
+                                       foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
+                                               $line = trim( $line );
+
+                                               if ( $line ) {
+                                                       $this->delayedParserTest->requireHook( $line );
+                                               }
+                                       }
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'endfunctionhooks' ) {
+                                       $this->checkSection( 'functionhooks' );
+
+                                       foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
+                                               $line = trim( $line );
+
+                                               if ( $line ) {
+                                                       $this->delayedParserTest->requireFunctionHook( $line );
+                                               }
+                                       }
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'endtransparenthooks' ) {
+                                       $this->checkSection( 'transparenthooks' );
+
+                                       foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
+                                               $line = trim( $line );
+
+                                               if ( $line ) {
+                                                       $this->delayedParserTest->requireTransparentHook( $line );
+                                               }
+                                       }
+
+                                       $this->clearSection();
+
+                                       continue;
+                               }
+
+                               if ( $this->section == 'end' ) {
+                                       $this->checkSection( 'test' );
+                                       do {
+                                               if ( $this->setupCurrentTest() ) {
+                                                       return true;
+                                               }
+                                       } while ( $this->nextSubTest > 0 );
+                                       # go on to next test (since this was disabled)
+                                       $this->clearSection();
+                                       $this->delayedParserTest->reset();
+                                       continue;
+                               }
+
+                               if ( isset( $this->sectionData[$this->section] ) ) {
+                                       throw new MWException( "duplicate section '$this->section' "
+                                               . "at line {$this->lineNum} of $this->file\n" );
+                               }
+
+                               $this->sectionData[$this->section] = '';
+
+                               continue;
+                       }
+
+                       if ( $this->section ) {
+                               $this->sectionData[$this->section] .= $line;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Clear section name and its data
+        */
+       private function clearSection() {
+               $this->sectionData = [];
+               $this->section = null;
+
+       }
+
+       /**
+        * Verify the current section data has some value for the given token
+        * name(s) (first parameter).
+        * Throw an exception if it is not set, referencing current section
+        * and adding the current file name and line number
+        *
+        * @param string|array $tokens Expected token(s) that should have been
+        * mentioned before closing this section
+        * @param bool $fatal True iff an exception should be thrown if
+        * the section is not found.
+        * @return bool|string
+        * @throws MWException
+        */
+       private function checkSection( $tokens, $fatal = true ) {
+               if ( is_null( $this->section ) ) {
+                       throw new MWException( __METHOD__ . " can not verify a null section!\n" );
+               }
+               if ( !is_array( $tokens ) ) {
+                       $tokens = [ $tokens ];
+               }
+               if ( count( $tokens ) == 0 ) {
+                       throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
+               }
+
+               $data = $this->sectionData;
+               $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
+                       return isset( $data[$token] );
+               } );
+
+               if ( count( $tokens ) == 0 ) {
+                       if ( !$fatal ) {
+                               return false;
+                       }
+                       throw new MWException( sprintf(
+                               "'%s' without '%s' at line %s of %s\n",
+                               $this->section,
+                               implode( ',', $tokens ),
+                               $this->lineNum,
+                               $this->file
+                       ) );
+               }
+               if ( count( $tokens ) > 1 ) {
+                       throw new MWException( sprintf(
+                               "'%s' with unexpected tokens '%s' at line %s of %s\n",
+                               $this->section,
+                               implode( ',', $tokens ),
+                               $this->lineNum,
+                               $this->file
+                       ) );
+               }
+
+               return array_values( $tokens )[0];
+       }
+}
+
diff --git a/tests/parser/TestRecorder.php b/tests/parser/TestRecorder.php
new file mode 100644 (file)
index 0000000..2608420
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * 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 Testing
+ */
+
+class TestRecorder implements ITestRecorder {
+       public $parent;
+       public $term;
+
+       function __construct( $parent ) {
+               $this->parent = $parent;
+               $this->term = $parent->term;
+       }
+
+       function start() {
+               $this->total = 0;
+               $this->success = 0;
+       }
+
+       function record( $test, $subtest, $result ) {
+               $this->total++;
+               $this->success += ( $result ? 1 : 0 );
+       }
+
+       function end() {
+               // dummy
+       }
+
+       function report() {
+               if ( $this->total > 0 ) {
+                       $this->reportPercentage( $this->success, $this->total );
+               } else {
+                       throw new MWException( "No tests found.\n" );
+               }
+       }
+
+       function reportPercentage( $success, $total ) {
+               $ratio = wfPercent( 100 * $success / $total );
+               print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... ";
+
+               if ( $success == $total ) {
+                       print $this->term->color( 32 ) . "ALL TESTS PASSED!";
+               } else {
+                       $failed = $total - $success;
+                       print $this->term->color( 31 ) . "$failed tests failed!";
+               }
+
+               print $this->term->reset() . "\n";
+
+               return ( $success == $total );
+       }
+}
+
diff --git a/tests/parser/TidySupport.php b/tests/parser/TidySupport.php
new file mode 100644 (file)
index 0000000..6b5fb48
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * 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 Testing
+ */
+
+/**
+ * Initialize and detect the tidy support
+ */
+class TidySupport {
+       private $enabled;
+       private $config;
+
+       /**
+        * Determine if there is a usable tidy.
+        */
+       public function __construct( $useConfiguration = false ) {
+               global $IP, $wgUseTidy, $wgTidyBin, $wgTidyInternal, $wgTidyConfig,
+                       $wgTidyConf, $wgTidyOpts;
+
+               $this->enabled = true;
+               if ( $useConfiguration ) {
+                       if ( $wgTidyConfig !== null ) {
+                               $this->config = $wgTidyConfig;
+                       } elseif ( $wgUseTidy ) {
+                               $this->config = [
+                                       'tidyConfigFile' => $wgTidyConf,
+                                       'debugComment' => false,
+                                       'tidyBin' => $wgTidyBin,
+                                       'tidyCommandLine' => $wgTidyOpts
+                               ];
+                               if ( $wgTidyInternal ) {
+                                       $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
+                               } else {
+                                       $this->config['driver'] = 'RaggettExternal';
+                               }
+                       } else {
+                               $this->enabled = false;
+                       }
+               } else {
+                       $this->config = [
+                               'tidyConfigFile' => "$IP/includes/tidy/tidy.conf",
+                               'tidyCommandLine' => '',
+                       ];
+                       if ( extension_loaded( 'tidy' ) && class_exists( 'tidy' ) ) {
+                               $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
+                       } else {
+                               if ( is_executable( $wgTidyBin ) ) {
+                                       $this->config['driver'] = 'RaggettExternal';
+                                       $this->config['tidyBin'] = $wgTidyBin;
+                               } else {
+                                       $path = Installer::locateExecutableInDefaultPaths( $wgTidyBin );
+                                       if ( $path !== false ) {
+                                               $this->config['driver'] = 'RaggettExternal';
+                                               $this->config['tidyBin'] = $wgTidyBin;
+                                       } else {
+                                               $this->enabled = false;
+                                       }
+                               }
+                       }
+               }
+               if ( !$this->enabled ) {
+                       $this->config = [ 'driver' => 'disabled' ];
+               }
+       }
+
+       /**
+        * Returns true if tidy is usable
+        *
+        * @return bool
+        */
+       public function isEnabled() {
+               return $this->enabled;
+       }
+
+       public function getConfig() {
+               return $this->config;
+       }
+}
diff --git a/tests/parser/fuzzTest.php b/tests/parser/fuzzTest.php
new file mode 100644 (file)
index 0000000..045a770
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+
+require __DIR__ . '/../../maintenance/Maintenance.php';
+
+// Make RequestContext::resetMain() happy
+define( 'MW_PARSER_TEST', 1 );
+
+class ParserFuzzTest extends Maintenance {
+       private $parserTest;
+       private $maxFuzzTestLength = 300;
+       private $memoryLimit = 100;
+       private $seed;
+
+       function __construct() {
+               parent::__construct();
+               $this->addDescription( 'Run a fuzz test on the parser, until it segfaults ' .
+                       'or throws an exception' );
+               $this->addOption( 'file', 'Use the specified file as a dictionary, ' .
+                       ' or leave blank to use parserTests.txt', false, true, true );
+
+               $this->addOption( 'seed', 'Start the fuzz test from the specified seed', false, true );
+       }
+
+       function finalSetup() {
+               require_once __DIR__ . '/../TestsAutoLoader.php';
+       }
+
+       function execute() {
+               $files = $this->getOption( 'file', [ __DIR__ . '/parserTests.txt' ] );
+               $this->seed = intval( $this->getOption( 'seed', 1 ) ) - 1;
+               $this->parserTest = new ParserTest;
+               $this->fuzzTest( $files );
+       }
+
+       /**
+        * Run a fuzz test series
+        * Draw input from a set of test files
+        * @param array $filenames
+        */
+       function fuzzTest( $filenames ) {
+               $GLOBALS['wgContLang'] = Language::factory( 'en' );
+               $dict = $this->getFuzzInput( $filenames );
+               $dictSize = strlen( $dict );
+               $logMaxLength = log( $this->maxFuzzTestLength );
+               $this->parserTest->setupDatabase();
+               ini_set( 'memory_limit', $this->memoryLimit * 1048576 * 2 );
+
+               $numTotal = 0;
+               $numSuccess = 0;
+               $user = new User;
+               $opts = ParserOptions::newFromUser( $user );
+               $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
+
+               while ( true ) {
+                       // Generate test input
+                       mt_srand( ++$this->seed );
+                       $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
+                       $input = '';
+
+                       while ( strlen( $input ) < $totalLength ) {
+                               $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
+                               $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
+                               $offset = mt_rand( 0, $dictSize - $hairLength );
+                               $input .= substr( $dict, $offset, $hairLength );
+                       }
+
+                       $this->parserTest->setupGlobals();
+                       $parser = $this->parserTest->getParser();
+
+                       // Run the test
+                       try {
+                               $parser->parse( $input, $title, $opts );
+                               $fail = false;
+                       } catch ( Exception $exception ) {
+                               $fail = true;
+                       }
+
+                       if ( $fail ) {
+                               echo "Test failed with seed {$this->seed}\n";
+                               echo "Input:\n";
+                               printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
+                               echo "$exception\n";
+                       } else {
+                               $numSuccess++;
+                       }
+
+                       $numTotal++;
+                       $this->parserTest->teardownGlobals();
+                       $parser->__destruct();
+
+                       if ( $numTotal % 100 == 0 ) {
+                               $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
+                               echo "{$this->seed}: $numSuccess/$numTotal (mem: $usage%)\n";
+                               if ( $usage >= 100 ) {
+                                       echo "Out of memory:\n";
+                                       $memStats = $this->getMemoryBreakdown();
+
+                                       foreach ( $memStats as $name => $usage ) {
+                                               echo "$name: $usage\n";
+                                       }
+                                       if ( function_exists( 'hphpd_break' ) ) {
+                                               hphpd_break();
+                                       }
+                                       return;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Get a memory usage breakdown
+        * @return array
+        */
+       function getMemoryBreakdown() {
+               $memStats = [];
+
+               foreach ( $GLOBALS as $name => $value ) {
+                       $memStats['$' . $name] = $this->guessVarSize( $value );
+               }
+
+               $classes = get_declared_classes();
+
+               foreach ( $classes as $class ) {
+                       $rc = new ReflectionClass( $class );
+                       $props = $rc->getStaticProperties();
+                       $memStats[$class] = $this->guessVarSize( $props );
+                       $methods = $rc->getMethods();
+
+                       foreach ( $methods as $method ) {
+                               $memStats[$class] += $this->guessVarSize( $method->getStaticVariables() );
+                       }
+               }
+
+               $functions = get_defined_functions();
+
+               foreach ( $functions['user'] as $function ) {
+                       $rf = new ReflectionFunction( $function );
+                       $memStats["$function()"] = $this->guessVarSize( $rf->getStaticVariables() );
+               }
+
+               asort( $memStats );
+
+               return $memStats;
+       }
+
+       /**
+        * Estimate the size of the input variable
+        */
+       function guessVarSize( $var ) {
+               $length = 0;
+               try {
+                       MediaWiki\suppressWarnings();
+                       $length = strlen( serialize( $var ) );
+                       MediaWiki\restoreWarnings();
+               } catch ( Exception $e ) {
+               }
+               return $length;
+       }
+
+       /**
+        * Get an input dictionary from a set of parser test files
+        * @param array $filenames
+        * @return string
+        */
+       function getFuzzInput( $filenames ) {
+               $dict = '';
+
+               foreach ( $filenames as $filename ) {
+                       $contents = file_get_contents( $filename );
+                       preg_match_all(
+                               '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
+                               $contents,
+                               $matches
+                       );
+
+                       foreach ( $matches[1] as $match ) {
+                               $dict .= $match . "\n";
+                       }
+               }
+
+               return $dict;
+       }
+}
+
+$maintClass = 'ParserFuzzTest';
+require RUN_MAINTENANCE_IF_MAIN;
diff --git a/tests/parser/parserTest.inc b/tests/parser/parserTest.inc
deleted file mode 100644 (file)
index e965e2d..0000000
+++ /dev/null
@@ -1,1815 +0,0 @@
-<?php
-/**
- * Helper code for the MediaWiki parser test suite. Some code is duplicated
- * in PHPUnit's NewParserTests.php, so you'll probably want to update both
- * at the same time.
- *
- * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @todo Make this more independent of the configuration (and if possible the database)
- * @todo document
- * @file
- * @ingroup Testing
- */
-use MediaWiki\MediaWikiServices;
-
-/**
- * @ingroup Testing
- */
-class ParserTest {
-       /**
-        * @var bool $color whereas output should be colorized
-        */
-       private $color;
-
-       /**
-        * @var bool $showOutput Show test output
-        */
-       private $showOutput;
-
-       /**
-        * @var bool $useTemporaryTables Use temporary tables for the temporary database
-        */
-       private $useTemporaryTables = true;
-
-       /**
-        * @var bool $databaseSetupDone True if the database has been set up
-        */
-       private $databaseSetupDone = false;
-
-       /**
-        * Our connection to the database
-        * @var DatabaseBase
-        */
-       private $db;
-
-       /**
-        * Database clone helper
-        * @var CloneDatabase
-        */
-       private $dbClone;
-
-       /**
-        * @var DjVuSupport
-        */
-       private $djVuSupport;
-
-       /**
-        * @var TidySupport
-        */
-       private $tidySupport;
-
-       /**
-        * @var ITestRecorder
-        */
-       private $recorder;
-
-       private $maxFuzzTestLength = 300;
-       private $fuzzSeed = 0;
-       private $memoryLimit = 50;
-       private $uploadDir = null;
-
-       public $regex = "";
-       private $savedGlobals = [];
-       private $useDwdiff = false;
-       private $markWhitespace = false;
-       private $normalizationFunctions = [];
-
-       /**
-        * Sets terminal colorization and diff/quick modes depending on OS and
-        * command-line options (--color and --quick).
-        * @param array $options
-        */
-       public function __construct( $options = [] ) {
-               # Only colorize output if stdout is a terminal.
-               $this->color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
-
-               if ( isset( $options['color'] ) ) {
-                       switch ( $options['color'] ) {
-                               case 'no':
-                                       $this->color = false;
-                                       break;
-                               case 'yes':
-                               default:
-                                       $this->color = true;
-                                       break;
-                       }
-               }
-
-               $this->term = $this->color
-                       ? new AnsiTermColorer()
-                       : new DummyTermColorer();
-
-               $this->showDiffs = !isset( $options['quick'] );
-               $this->showProgress = !isset( $options['quiet'] );
-               $this->showFailure = !(
-                       isset( $options['quiet'] )
-                               && ( isset( $options['record'] )
-                               || isset( $options['compare'] ) ) ); // redundant output
-
-               $this->showOutput = isset( $options['show-output'] );
-               $this->useDwdiff = isset( $options['dwdiff'] );
-               $this->markWhitespace = isset( $options['mark-ws'] );
-
-               if ( isset( $options['norm'] ) ) {
-                       foreach ( explode( ',', $options['norm'] ) as $func ) {
-                               if ( in_array( $func, [ 'removeTbody', 'trimWhitespace' ] ) ) {
-                                       $this->normalizationFunctions[] = $func;
-                               } else {
-                                       echo "Warning: unknown normalization option \"$func\"\n";
-                               }
-                       }
-               }
-
-               if ( isset( $options['filter'] ) ) {
-                       $options['regex'] = $options['filter'];
-               }
-
-               if ( isset( $options['regex'] ) ) {
-                       if ( isset( $options['record'] ) ) {
-                               echo "Warning: --record cannot be used with --regex, disabling --record\n";
-                               unset( $options['record'] );
-                       }
-                       $this->regex = $options['regex'];
-               } else {
-                       # Matches anything
-                       $this->regex = '';
-               }
-
-               $this->setupRecorder( $options );
-               $this->keepUploads = isset( $options['keep-uploads'] );
-
-               if ( $this->keepUploads ) {
-                       $this->uploadDir = wfTempDir() . '/mwParser-images';
-               } else {
-                       $this->uploadDir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
-               }
-
-               if ( isset( $options['seed'] ) ) {
-                       $this->fuzzSeed = intval( $options['seed'] ) - 1;
-               }
-
-               $this->runDisabled = isset( $options['run-disabled'] );
-               $this->runParsoid = isset( $options['run-parsoid'] );
-
-               $this->djVuSupport = new DjVuSupport();
-               $this->tidySupport = new TidySupport( isset( $options['use-tidy-config'] ) );
-               if ( !$this->tidySupport->isEnabled() ) {
-                       echo "Warning: tidy is not installed, skipping some tests\n";
-               }
-
-               $this->hooks = [];
-               $this->functionHooks = [];
-               $this->transparentHooks = [];
-               $this->setUp();
-       }
-
-       function setUp() {
-               global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
-                       $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
-                       $wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
-                       $wgExtraInterlanguageLinkPrefixes, $wgLocalInterwikis,
-                       $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, $wgResourceBasePath,
-                       $wgArticlePath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
-                       $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgLockManagers;
-
-               $wgScriptPath = '';
-               $wgScript = '/index.php';
-               $wgStylePath = '/skins';
-               $wgResourceBasePath = '';
-               $wgExtensionAssetsPath = '/extensions';
-               $wgArticlePath = '/wiki/$1';
-               $wgThumbnailScriptPath = false;
-               $wgLockManagers = [ [
-                       'name' => 'fsLockManager',
-                       'class' => 'FSLockManager',
-                       'lockDirectory' => $this->uploadDir . '/lockdir',
-               ], [
-                       'name' => 'nullLockManager',
-                       'class' => 'NullLockManager',
-               ] ];
-               $wgLocalFileRepo = [
-                       'class' => 'LocalRepo',
-                       'name' => 'local',
-                       'url' => 'http://example.com/images',
-                       'hashLevels' => 2,
-                       'transformVia404' => false,
-                       'backend' => new FSFileBackend( [
-                               'name' => 'local-backend',
-                               'wikiId' => wfWikiID(),
-                               'containerPaths' => [
-                                       'local-public' => $this->uploadDir . '/public',
-                                       'local-thumb' => $this->uploadDir . '/thumb',
-                                       'local-temp' => $this->uploadDir . '/temp',
-                                       'local-deleted' => $this->uploadDir . '/deleted',
-                               ]
-                       ] )
-               ];
-               $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
-               $wgNamespaceAliases['Image'] = NS_FILE;
-               $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
-               # add a namespace shadowing a interwiki link, to test
-               # proper precedence when resolving links. (bug 51680)
-               $wgExtraNamespaces[100] = 'MemoryAlpha';
-               $wgExtraNamespaces[101] = 'MemoryAlpha talk';
-
-               // XXX: tests won't run without this (for CACHE_DB)
-               if ( $wgMainCacheType === CACHE_DB ) {
-                       $wgMainCacheType = CACHE_NONE;
-               }
-               if ( $wgMessageCacheType === CACHE_DB ) {
-                       $wgMessageCacheType = CACHE_NONE;
-               }
-               if ( $wgParserCacheType === CACHE_DB ) {
-                       $wgParserCacheType = CACHE_NONE;
-               }
-
-               DeferredUpdates::clearPendingUpdates();
-               $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
-               $messageMemc = wfGetMessageCacheStorage();
-               $parserMemc = wfGetParserCacheStorage();
-
-               RequestContext::resetMain();
-               $context = new RequestContext;
-               $wgUser = new User;
-               $wgLang = $context->getLanguage();
-               $wgOut = $context->getOutput();
-               $wgRequest = $context->getRequest();
-               $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], [ $wgParserConf ] );
-
-               if ( $wgStyleDirectory === false ) {
-                       $wgStyleDirectory = "$IP/skins";
-               }
-
-               self::setupInterwikis();
-               $wgLocalInterwikis = [ 'local', 'mi' ];
-               // "extra language links"
-               // see https://gerrit.wikimedia.org/r/111390
-               array_push( $wgExtraInterlanguageLinkPrefixes, 'mul' );
-
-               // Reset namespace cache
-               MWNamespace::getCanonicalNamespaces( true );
-               Language::factory( 'en' )->resetNamespaces();
-       }
-
-       /**
-        * Insert hardcoded interwiki in the lookup table.
-        *
-        * This function insert a set of well known interwikis that are used in
-        * the parser tests. They can be considered has fixtures are injected in
-        * the interwiki cache by using the 'InterwikiLoadPrefix' hook.
-        * Since we are not interested in looking up interwikis in the database,
-        * the hook completely replace the existing mechanism (hook returns false).
-        */
-       public static function setupInterwikis() {
-               # Hack: insert a few Wikipedia in-project interwiki prefixes,
-               # for testing inter-language links
-               Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
-                       static $testInterwikis = [
-                               'local' => [
-                                       'iw_url' => 'http://doesnt.matter.org/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'wikipedia' => [
-                                       'iw_url' => 'http://en.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'meatball' => [
-                                       'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'memoryalpha' => [
-                                       'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 0 ],
-                               'zh' => [
-                                       'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'es' => [
-                                       'iw_url' => 'http://es.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'fr' => [
-                                       'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'ru' => [
-                                       'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'mi' => [
-                                       'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                               'mul' => [
-                                       'iw_url' => 'http://wikisource.org/wiki/$1',
-                                       'iw_api' => '',
-                                       'iw_wikiid' => '',
-                                       'iw_local' => 1 ],
-                       ];
-                       if ( array_key_exists( $prefix, $testInterwikis ) ) {
-                               $iwData = $testInterwikis[$prefix];
-                       }
-
-                       // We only want to rely on the above fixtures
-                       return false;
-               } );// hooks::register
-       }
-
-       /**
-        * Remove the hardcoded interwiki lookup table.
-        */
-       public static function tearDownInterwikis() {
-               Hooks::clear( 'InterwikiLoadPrefix' );
-       }
-
-       /**
-        * Reset the Title-related services that need resetting
-        * for each test
-        */
-       public static function resetTitleServices() {
-               $services = MediaWikiServices::getInstance();
-               $services->resetServiceForTesting( 'TitleFormatter' );
-               $services->resetServiceForTesting( 'TitleParser' );
-               $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
-               $services->resetServiceForTesting( 'LinkRenderer' );
-               $services->resetServiceForTesting( 'LinkRendererFactory' );
-       }
-
-       public function setupRecorder( $options ) {
-               if ( isset( $options['record'] ) ) {
-                       $this->recorder = new DbTestRecorder( $this );
-                       $this->recorder->version = isset( $options['setversion'] ) ?
-                               $options['setversion'] : SpecialVersion::getVersion();
-               } elseif ( isset( $options['compare'] ) ) {
-                       $this->recorder = new DbTestPreviewer( $this );
-               } else {
-                       $this->recorder = new TestRecorder( $this );
-               }
-       }
-
-       /**
-        * Remove last character if it is a newline
-        * @group utility
-        * @param string $s
-        * @return string
-        */
-       public static function chomp( $s ) {
-               if ( substr( $s, -1 ) === "\n" ) {
-                       return substr( $s, 0, -1 );
-               } else {
-                       return $s;
-               }
-       }
-
-       /**
-        * Run a fuzz test series
-        * Draw input from a set of test files
-        * @param array $filenames
-        */
-       function fuzzTest( $filenames ) {
-               $GLOBALS['wgContLang'] = Language::factory( 'en' );
-               $dict = $this->getFuzzInput( $filenames );
-               $dictSize = strlen( $dict );
-               $logMaxLength = log( $this->maxFuzzTestLength );
-               $this->setupDatabase();
-               ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
-
-               $numTotal = 0;
-               $numSuccess = 0;
-               $user = new User;
-               $opts = ParserOptions::newFromUser( $user );
-               $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
-
-               while ( true ) {
-                       // Generate test input
-                       mt_srand( ++$this->fuzzSeed );
-                       $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
-                       $input = '';
-
-                       while ( strlen( $input ) < $totalLength ) {
-                               $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
-                               $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
-                               $offset = mt_rand( 0, $dictSize - $hairLength );
-                               $input .= substr( $dict, $offset, $hairLength );
-                       }
-
-                       $this->setupGlobals();
-                       $parser = $this->getParser();
-
-                       // Run the test
-                       try {
-                               $parser->parse( $input, $title, $opts );
-                               $fail = false;
-                       } catch ( Exception $exception ) {
-                               $fail = true;
-                       }
-
-                       if ( $fail ) {
-                               echo "Test failed with seed {$this->fuzzSeed}\n";
-                               echo "Input:\n";
-                               printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
-                               echo "$exception\n";
-                       } else {
-                               $numSuccess++;
-                       }
-
-                       $numTotal++;
-                       $this->teardownGlobals();
-                       $parser->__destruct();
-
-                       if ( $numTotal % 100 == 0 ) {
-                               $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
-                               echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
-                               if ( $usage > 90 ) {
-                                       echo "Out of memory:\n";
-                                       $memStats = $this->getMemoryBreakdown();
-
-                                       foreach ( $memStats as $name => $usage ) {
-                                               echo "$name: $usage\n";
-                                       }
-                                       $this->abort();
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Get an input dictionary from a set of parser test files
-        * @param array $filenames
-        * @return string
-        */
-       function getFuzzInput( $filenames ) {
-               $dict = '';
-
-               foreach ( $filenames as $filename ) {
-                       $contents = file_get_contents( $filename );
-                       preg_match_all(
-                               '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
-                               $contents,
-                               $matches
-                       );
-
-                       foreach ( $matches[1] as $match ) {
-                               $dict .= $match . "\n";
-                       }
-               }
-
-               return $dict;
-       }
-
-       /**
-        * Get a memory usage breakdown
-        * @return array
-        */
-       function getMemoryBreakdown() {
-               $memStats = [];
-
-               foreach ( $GLOBALS as $name => $value ) {
-                       $memStats['$' . $name] = strlen( serialize( $value ) );
-               }
-
-               $classes = get_declared_classes();
-
-               foreach ( $classes as $class ) {
-                       $rc = new ReflectionClass( $class );
-                       $props = $rc->getStaticProperties();
-                       $memStats[$class] = strlen( serialize( $props ) );
-                       $methods = $rc->getMethods();
-
-                       foreach ( $methods as $method ) {
-                               $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
-                       }
-               }
-
-               $functions = get_defined_functions();
-
-               foreach ( $functions['user'] as $function ) {
-                       $rf = new ReflectionFunction( $function );
-                       $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
-               }
-
-               asort( $memStats );
-
-               return $memStats;
-       }
-
-       function abort() {
-               $this->abort();
-       }
-
-       /**
-        * Run a series of tests listed in the given text files.
-        * Each test consists of a brief description, wikitext input,
-        * and the expected HTML output.
-        *
-        * Prints status updates on stdout and counts up the total
-        * number and percentage of passed tests.
-        *
-        * @param array $filenames Array of strings
-        * @return bool True if passed all tests, false if any tests failed.
-        */
-       public function runTestsFromFiles( $filenames ) {
-               $ok = false;
-
-               // be sure, ParserTest::addArticle has correct language set,
-               // so that system messages gets into the right language cache
-               $GLOBALS['wgLanguageCode'] = 'en';
-               $GLOBALS['wgContLang'] = Language::factory( 'en' );
-
-               $this->recorder->start();
-               try {
-                       $this->setupDatabase();
-                       $ok = true;
-
-                       foreach ( $filenames as $filename ) {
-                               echo "Running parser tests from: $filename\n";
-                               $tests = new TestFileIterator( $filename, $this );
-                               $ok = $this->runTests( $tests ) && $ok;
-                       }
-
-                       $this->teardownDatabase();
-                       $this->recorder->report();
-               } catch ( DBError $e ) {
-                       echo $e->getMessage();
-               }
-               $this->recorder->end();
-
-               return $ok;
-       }
-
-       function runTests( $tests ) {
-               $ok = true;
-
-               foreach ( $tests as $t ) {
-                       $result =
-                               $this->runTest( $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] );
-                       $ok = $ok && $result;
-                       $this->recorder->record( $t['test'], $t['subtest'], $result );
-               }
-
-               if ( $this->showProgress ) {
-                       print "\n";
-               }
-
-               return $ok;
-       }
-
-       /**
-        * Get a Parser object
-        *
-        * @param string $preprocessor
-        * @return Parser
-        */
-       function getParser( $preprocessor = null ) {
-               global $wgParserConf;
-
-               $class = $wgParserConf['class'];
-               $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
-
-               foreach ( $this->hooks as $tag => $callback ) {
-                       $parser->setHook( $tag, $callback );
-               }
-
-               foreach ( $this->functionHooks as $tag => $bits ) {
-                       list( $callback, $flags ) = $bits;
-                       $parser->setFunctionHook( $tag, $callback, $flags );
-               }
-
-               foreach ( $this->transparentHooks as $tag => $callback ) {
-                       $parser->setTransparentTagHook( $tag, $callback );
-               }
-
-               Hooks::run( 'ParserTestParser', [ &$parser ] );
-
-               return $parser;
-       }
-
-       /**
-        * Run a given wikitext input through a freshly-constructed wiki parser,
-        * and compare the output against the expected results.
-        * Prints status and explanatory messages to stdout.
-        *
-        * @param string $desc Test's description
-        * @param string $input Wikitext to try rendering
-        * @param string $result Result to output
-        * @param array $opts Test's options
-        * @param string $config Overrides for global variables, one per line
-        * @return bool
-        */
-       public function runTest( $desc, $input, $result, $opts, $config ) {
-               if ( $this->showProgress ) {
-                       $this->showTesting( $desc );
-               }
-
-               $opts = $this->parseOptions( $opts );
-               $context = $this->setupGlobals( $opts, $config );
-
-               $user = $context->getUser();
-               $options = ParserOptions::newFromContext( $context );
-
-               if ( isset( $opts['djvu'] ) ) {
-                       if ( !$this->djVuSupport->isEnabled() ) {
-                               return $this->showSkipped();
-                       }
-               }
-
-               if ( isset( $opts['tidy'] ) ) {
-                       if ( !$this->tidySupport->isEnabled() ) {
-                               return $this->showSkipped();
-                       } else {
-                               $options->setTidy( true );
-                       }
-               }
-
-               if ( isset( $opts['title'] ) ) {
-                       $titleText = $opts['title'];
-               } else {
-                       $titleText = 'Parser test';
-               }
-
-               ObjectCache::getMainWANInstance()->clearProcessCache();
-               $local = isset( $opts['local'] );
-               $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
-               $parser = $this->getParser( $preprocessor );
-               $title = Title::newFromText( $titleText );
-
-               if ( isset( $opts['pst'] ) ) {
-                       $out = $parser->preSaveTransform( $input, $title, $user, $options );
-               } elseif ( isset( $opts['msg'] ) ) {
-                       $out = $parser->transformMsg( $input, $options, $title );
-               } elseif ( isset( $opts['section'] ) ) {
-                       $section = $opts['section'];
-                       $out = $parser->getSection( $input, $section );
-               } elseif ( isset( $opts['replace'] ) ) {
-                       $section = $opts['replace'][0];
-                       $replace = $opts['replace'][1];
-                       $out = $parser->replaceSection( $input, $section, $replace );
-               } elseif ( isset( $opts['comment'] ) ) {
-                       $out = Linker::formatComment( $input, $title, $local );
-               } elseif ( isset( $opts['preload'] ) ) {
-                       $out = $parser->getPreloadText( $input, $title, $options );
-               } else {
-                       $output = $parser->parse( $input, $title, $options, true, true, 1337 );
-                       $output->setTOCEnabled( !isset( $opts['notoc'] ) );
-                       $out = $output->getText();
-                       if ( isset( $opts['tidy'] ) ) {
-                               $out = preg_replace( '/\s+$/', '', $out );
-                       }
-
-                       if ( isset( $opts['showtitle'] ) ) {
-                               if ( $output->getTitleText() ) {
-                                       $title = $output->getTitleText();
-                               }
-
-                               $out = "$title\n$out";
-                       }
-
-                       if ( isset( $opts['showindicators'] ) ) {
-                               $indicators = '';
-                               foreach ( $output->getIndicators() as $id => $content ) {
-                                       $indicators .= "$id=$content\n";
-                               }
-                               $out = $indicators . $out;
-                       }
-
-                       if ( isset( $opts['ill'] ) ) {
-                               $out = implode( ' ', $output->getLanguageLinks() );
-                       } elseif ( isset( $opts['cat'] ) ) {
-                               $outputPage = $context->getOutput();
-                               $outputPage->addCategoryLinks( $output->getCategories() );
-                               $cats = $outputPage->getCategoryLinks();
-
-                               if ( isset( $cats['normal'] ) ) {
-                                       $out = implode( ' ', $cats['normal'] );
-                               } else {
-                                       $out = '';
-                               }
-                       }
-               }
-
-               $this->teardownGlobals();
-
-               if ( count( $this->normalizationFunctions ) ) {
-                       $result = ParserTestResultNormalizer::normalize( $result, $this->normalizationFunctions );
-                       $out = ParserTestResultNormalizer::normalize( $out, $this->normalizationFunctions );
-               }
-
-               $testResult = new ParserTestResult( $desc );
-               $testResult->expected = $result;
-               $testResult->actual = $out;
-
-               return $this->showTestResult( $testResult );
-       }
-
-       /**
-        * Refactored in 1.22 to use ParserTestResult
-        * @param ParserTestResult $testResult
-        * @return bool
-        */
-       function showTestResult( ParserTestResult $testResult ) {
-               if ( $testResult->isSuccess() ) {
-                       $this->showSuccess( $testResult );
-                       return true;
-               } else {
-                       $this->showFailure( $testResult );
-                       return false;
-               }
-       }
-
-       /**
-        * Use a regex to find out the value of an option
-        * @param string $key Name of option val to retrieve
-        * @param array $opts Options array to look in
-        * @param mixed $default Default value returned if not found
-        * @return mixed
-        */
-       private static function getOptionValue( $key, $opts, $default ) {
-               $key = strtolower( $key );
-
-               if ( isset( $opts[$key] ) ) {
-                       return $opts[$key];
-               } else {
-                       return $default;
-               }
-       }
-
-       private function parseOptions( $instring ) {
-               $opts = [];
-               // foo
-               // foo=bar
-               // foo="bar baz"
-               // foo=[[bar baz]]
-               // foo=bar,"baz quux"
-               // foo={...json...}
-               $defs = '(?(DEFINE)
-                       (?<qstr>                                        # Quoted string
-                               "
-                               (?:[^\\\\"] | \\\\.)*
-                               "
-                       )
-                       (?<json>
-                               \{              # Open bracket
-                               (?:
-                                       [^"{}] |                                # Not a quoted string or object, or
-                                       (?&qstr) |                              # A quoted string, or
-                                       (?&json)                                # A json object (recursively)
-                               )*
-                               \}              # Close bracket
-                       )
-                       (?<value>
-                               (?:
-                                       (?&qstr)                        # Quoted val
-                               |
-                                       \[\[
-                                               [^]]*                   # Link target
-                                       \]\]
-                               |
-                                       [\w-]+                          # Plain word
-                               |
-                                       (?&json)                        # JSON object
-                               )
-                       )
-               )';
-               $regex = '/' . $defs . '\b
-                       (?<k>[\w-]+)                            # Key
-                       \b
-                       (?:\s*
-                               =                                               # First sub-value
-                               \s*
-                               (?<v>
-                                       (?&value)
-                                       (?:\s*
-                                               ,                               # Sub-vals 1..N
-                                               \s*
-                                               (?&value)
-                                       )*
-                               )
-                       )?
-                       /x';
-               $valueregex = '/' . $defs . '(?&value)/x';
-
-               if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
-                       foreach ( $matches as $bits ) {
-                               $key = strtolower( $bits['k'] );
-                               if ( !isset( $bits['v'] ) ) {
-                                       $opts[$key] = true;
-                               } else {
-                                       preg_match_all( $valueregex, $bits['v'], $vmatches );
-                                       $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
-                                       if ( count( $opts[$key] ) == 1 ) {
-                                               $opts[$key] = $opts[$key][0];
-                                       }
-                               }
-                       }
-               }
-               return $opts;
-       }
-
-       private function cleanupOption( $opt ) {
-               if ( substr( $opt, 0, 1 ) == '"' ) {
-                       return stripcslashes( substr( $opt, 1, -1 ) );
-               }
-
-               if ( substr( $opt, 0, 2 ) == '[[' ) {
-                       return substr( $opt, 2, -2 );
-               }
-
-               if ( substr( $opt, 0, 1 ) == '{' ) {
-                       return FormatJson::decode( $opt, true );
-               }
-               return $opt;
-       }
-
-       /**
-        * Set up the global variables for a consistent environment for each test.
-        * Ideally this should replace the global configuration entirely.
-        * @param string $opts
-        * @param string $config
-        * @return RequestContext
-        */
-       private function setupGlobals( $opts = '', $config = '' ) {
-               # Find out values for some special options.
-               $lang =
-                       self::getOptionValue( 'language', $opts, 'en' );
-               $variant =
-                       self::getOptionValue( 'variant', $opts, false );
-               $maxtoclevel =
-                       self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
-               $linkHolderBatchSize =
-                       self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
-
-               $settings = [
-                       'wgServer' => 'http://example.org',
-                       'wgServerName' => 'example.org',
-                       'wgScript' => '/index.php',
-                       'wgScriptPath' => '',
-                       'wgArticlePath' => '/wiki/$1',
-                       'wgActionPaths' => [],
-                       'wgLockManagers' => [ [
-                               'name' => 'fsLockManager',
-                               'class' => 'FSLockManager',
-                               'lockDirectory' => $this->uploadDir . '/lockdir',
-                       ], [
-                               'name' => 'nullLockManager',
-                               'class' => 'NullLockManager',
-                       ] ],
-                       'wgLocalFileRepo' => [
-                               'class' => 'LocalRepo',
-                               'name' => 'local',
-                               'url' => 'http://example.com/images',
-                               'hashLevels' => 2,
-                               'transformVia404' => false,
-                               'backend' => new FSFileBackend( [
-                                       'name' => 'local-backend',
-                                       'wikiId' => wfWikiID(),
-                                       'containerPaths' => [
-                                               'local-public' => $this->uploadDir,
-                                               'local-thumb' => $this->uploadDir . '/thumb',
-                                               'local-temp' => $this->uploadDir . '/temp',
-                                               'local-deleted' => $this->uploadDir . '/delete',
-                                       ]
-                               ] )
-                       ],
-                       'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
-                       'wgUploadNavigationUrl' => false,
-                       'wgStylePath' => '/skins',
-                       'wgSitename' => 'MediaWiki',
-                       'wgLanguageCode' => $lang,
-                       'wgDBprefix' => $this->db->getType() != 'oracle' ? 'parsertest_' : 'pt_',
-                       'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
-                       'wgLang' => null,
-                       'wgContLang' => null,
-                       'wgNamespacesWithSubpages' => [ 0 => isset( $opts['subpage'] ) ],
-                       'wgMaxTocLevel' => $maxtoclevel,
-                       'wgCapitalLinks' => true,
-                       'wgNoFollowLinks' => true,
-                       'wgNoFollowDomainExceptions' => [ 'no-nofollow.org' ],
-                       'wgThumbnailScriptPath' => false,
-                       'wgUseImageResize' => true,
-                       'wgSVGConverter' => 'null',
-                       'wgSVGConverters' => [ 'null' => 'echo "1">$output' ],
-                       'wgLocaltimezone' => 'UTC',
-                       'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
-                       'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
-                       'wgDefaultLanguageVariant' => $variant,
-                       'wgVariantArticlePath' => false,
-                       'wgGroupPermissions' => [ '*' => [
-                               'createaccount' => true,
-                               'read' => true,
-                               'edit' => true,
-                               'createpage' => true,
-                               'createtalk' => true,
-                       ] ],
-                       'wgNamespaceProtection' => [ NS_MEDIAWIKI => 'editinterface' ],
-                       'wgDefaultExternalStore' => [],
-                       'wgForeignFileRepos' => [],
-                       'wgLinkHolderBatchSize' => $linkHolderBatchSize,
-                       'wgExperimentalHtmlIds' => false,
-                       'wgExternalLinkTarget' => false,
-                       'wgHtml5' => true,
-                       'wgAdaptiveMessageCache' => true,
-                       'wgDisableLangConversion' => false,
-                       'wgDisableTitleConversion' => false,
-                       // Tidy options.
-                       'wgUseTidy' => false,
-                       'wgTidyConfig' => isset( $opts['tidy'] ) ? $this->tidySupport->getConfig() : null
-               ];
-
-               if ( $config ) {
-                       $configLines = explode( "\n", $config );
-
-                       foreach ( $configLines as $line ) {
-                               list( $var, $value ) = explode( '=', $line, 2 );
-
-                               $settings[$var] = eval( "return $value;" );
-                       }
-               }
-
-               $this->savedGlobals = [];
-
-               /** @since 1.20 */
-               Hooks::run( 'ParserTestGlobals', [ &$settings ] );
-
-               foreach ( $settings as $var => $val ) {
-                       if ( array_key_exists( $var, $GLOBALS ) ) {
-                               $this->savedGlobals[$var] = $GLOBALS[$var];
-                       }
-
-                       $GLOBALS[$var] = $val;
-               }
-
-               // Must be set before $context as user language defaults to $wgContLang
-               $GLOBALS['wgContLang'] = Language::factory( $lang );
-               $GLOBALS['wgMemc'] = new EmptyBagOStuff;
-
-               RequestContext::resetMain();
-               $context = RequestContext::getMain();
-               $GLOBALS['wgLang'] = $context->getLanguage();
-               $GLOBALS['wgOut'] = $context->getOutput();
-               $GLOBALS['wgUser'] = $context->getUser();
-
-               // We (re)set $wgThumbLimits to a single-element array above.
-               $context->getUser()->setOption( 'thumbsize', 0 );
-
-               global $wgHooks;
-
-               $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
-               $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
-
-               MagicWord::clearCache();
-               MWTidy::destroySingleton();
-               RepoGroup::destroySingleton();
-
-               self::resetTitleServices();
-
-               return $context;
-       }
-
-       /**
-        * List of temporary tables to create, without prefix.
-        * Some of these probably aren't necessary.
-        * @return array
-        */
-       private function listTables() {
-               $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
-                       'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
-                       'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
-                       'site_stats', 'ipblocks', 'image', 'oldimage',
-                       'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
-                       'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
-                       'archive', 'user_groups', 'page_props', 'category'
-               ];
-
-               if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
-                       array_push( $tables, 'searchindex' );
-               }
-
-               // Allow extensions to add to the list of tables to duplicate;
-               // may be necessary if they hook into page save or other code
-               // which will require them while running tests.
-               Hooks::run( 'ParserTestTables', [ &$tables ] );
-
-               return $tables;
-       }
-
-       /**
-        * Set up a temporary set of wiki tables to work with for the tests.
-        * Currently this will only be done once per run, and any changes to
-        * the db will be visible to later tests in the run.
-        */
-       public function setupDatabase() {
-               global $wgDBprefix;
-
-               if ( $this->databaseSetupDone ) {
-                       return;
-               }
-
-               $this->db = wfGetDB( DB_MASTER );
-               $dbType = $this->db->getType();
-
-               if ( $wgDBprefix === 'parsertest_' || ( $dbType == 'oracle' && $wgDBprefix === 'pt_' ) ) {
-                       throw new MWException( 'setupDatabase should be called before setupGlobals' );
-               }
-
-               $this->databaseSetupDone = true;
-
-               # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
-               # It seems to have been fixed since (r55079?), but regressed at some point before r85701.
-               # This works around it for now...
-               ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
-
-               # CREATE TEMPORARY TABLE breaks if there is more than one server
-               if ( wfGetLB()->getServerCount() != 1 ) {
-                       $this->useTemporaryTables = false;
-               }
-
-               $temporary = $this->useTemporaryTables || $dbType == 'postgres';
-               $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
-
-               $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
-               $this->dbClone->useTemporaryTables( $temporary );
-               $this->dbClone->cloneTableStructure();
-
-               if ( $dbType == 'oracle' ) {
-                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
-                       # Insert 0 user to prevent FK violations
-
-                       # Anonymous user
-                       $this->db->insert( 'user', [
-                               'user_id' => 0,
-                               'user_name' => 'Anonymous' ] );
-               }
-
-               # Update certain things in site_stats
-               $this->db->insert( 'site_stats',
-                       [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ] );
-
-               # Reinitialise the LocalisationCache to match the database state
-               Language::getLocalisationCache()->unloadAll();
-
-               # Clear the message cache
-               MessageCache::singleton()->clear();
-
-               // Remember to update newParserTests.php after changing the below
-               // (and it uses a slightly different syntax just for teh lulz)
-               $this->setupUploadDir();
-               $user = User::createNew( 'WikiSysop' );
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
-               # note that the size/width/height/bits/etc of the file
-               # are actually set by inspecting the file itself; the arguments
-               # to recordUpload2 have no effect.  That said, we try to make things
-               # match up so it is less confusing to readers of the code & tests.
-               $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
-                       'size' => 7881,
-                       'width' => 1941,
-                       'height' => 220,
-                       'bits' => 8,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/jpeg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
-               # again, note that size/width/height below are ignored; see above.
-               $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
-                       'size' => 22589,
-                       'width' => 135,
-                       'height' => 135,
-                       'bits' => 8,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/png',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20130225203040' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
-               $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
-                               'size'        => 12345,
-                               'width'       => 240,
-                               'height'      => 180,
-                               'bits'        => 0,
-                               'media_type'  => MEDIATYPE_DRAWING,
-                               'mime'        => 'image/svg+xml',
-                               'metadata'    => serialize( [] ),
-                               'sha1'        => Wikimedia\base_convert( '', 16, 36, 31 ),
-                               'fileExists'  => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               # This image will be blacklisted in [[MediaWiki:Bad image list]]
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
-               $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
-                       'size' => 12345,
-                       'width' => 320,
-                       'height' => 240,
-                       'bits' => 24,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/jpeg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
-               $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
-                       'size' => 12345,
-                       'width' => 320,
-                       'height' => 240,
-                       'bits' => 0,
-                       'media_type' => MEDIATYPE_VIDEO,
-                       'mime' => 'application/ogg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
-               $image->recordUpload2( '', 'An awesome hitsong', 'Will it play', [
-                       'size' => 12345,
-                       'width' => 0,
-                       'height' => 0,
-                       'bits' => 0,
-                       'media_type' => MEDIATYPE_AUDIO,
-                       'mime' => 'application/ogg',
-                       'metadata' => serialize( [] ),
-                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123500' ), $user );
-
-               # A DjVu file
-               $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
-               $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
-                       'size' => 3249,
-                       'width' => 2480,
-                       'height' => 3508,
-                       'bits' => 0,
-                       'media_type' => MEDIATYPE_BITMAP,
-                       'mime' => 'image/vnd.djvu',
-                       'metadata' => '<?xml version="1.0" ?>
-<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
-<DjVuXML>
-<HEAD></HEAD>
-<BODY><OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-</BODY>
-</DjVuXML>',
-                       'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
-                       'fileExists' => true
-               ], $this->db->timestamp( '20010115123600' ), $user );
-       }
-
-       public function teardownDatabase() {
-               if ( !$this->databaseSetupDone ) {
-                       $this->teardownGlobals();
-                       return;
-               }
-               $this->teardownUploadDir( $this->uploadDir );
-
-               $this->dbClone->destroy();
-               $this->databaseSetupDone = false;
-
-               if ( $this->useTemporaryTables ) {
-                       if ( $this->db->getType() == 'sqlite' ) {
-                               # Under SQLite the searchindex table is virtual and need
-                               # to be explicitly destroyed. See bug 29912
-                               # See also MediaWikiTestCase::destroyDB()
-                               wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
-                               $this->db->query( "DROP TABLE `parsertest_searchindex`" );
-                       }
-                       # Don't need to do anything
-                       $this->teardownGlobals();
-                       return;
-               }
-
-               $tables = $this->listTables();
-
-               foreach ( $tables as $table ) {
-                       if ( $this->db->getType() == 'oracle' ) {
-                               $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
-                       } else {
-                               $this->db->query( "DROP TABLE `parsertest_$table`" );
-                       }
-               }
-
-               if ( $this->db->getType() == 'oracle' ) {
-                       $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
-               }
-
-               $this->teardownGlobals();
-       }
-
-       /**
-        * Create a dummy uploads directory which will contain a couple
-        * of files in order to pass existence tests.
-        *
-        * @return string The directory
-        */
-       private function setupUploadDir() {
-               global $IP;
-
-               $dir = $this->uploadDir;
-               if ( $this->keepUploads && is_dir( $dir ) ) {
-                       return;
-               }
-
-               // wfDebug( "Creating upload directory $dir\n" );
-               if ( file_exists( $dir ) ) {
-                       wfDebug( "Already exists!\n" );
-                       return;
-               }
-
-               wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
-               wfMkdirParents( $dir . '/e/ea', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/wiki.png", "$dir/e/ea/Thumb.png" );
-               wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/0/09/Bad.jpg" );
-               wfMkdirParents( $dir . '/f/ff', null, __METHOD__ );
-               file_put_contents( "$dir/f/ff/Foobar.svg",
-                       '<?xml version="1.0" encoding="utf-8"?>' .
-                       '<svg xmlns="http://www.w3.org/2000/svg"' .
-                       ' version="1.1" width="240" height="180"/>' );
-               wfMkdirParents( $dir . '/5/5f', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/LoremIpsum.djvu", "$dir/5/5f/LoremIpsum.djvu" );
-               wfMkdirParents( $dir . '/0/00', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/parser/320x240.ogv", "$dir/0/00/Video.ogv" );
-               wfMkdirParents( $dir . '/4/41', null, __METHOD__ );
-               copy( "$IP/tests/phpunit/data/media/say-test.ogg", "$dir/4/41/Audio.oga" );
-
-               return;
-       }
-
-       /**
-        * Restore default values and perform any necessary clean-up
-        * after each test runs.
-        */
-       private function teardownGlobals() {
-               RepoGroup::destroySingleton();
-               FileBackendGroup::destroySingleton();
-               LockManagerGroup::destroySingletons();
-               LinkCache::singleton()->clear();
-               MWTidy::destroySingleton();
-
-               foreach ( $this->savedGlobals as $var => $val ) {
-                       $GLOBALS[$var] = $val;
-               }
-       }
-
-       /**
-        * Remove the dummy uploads directory
-        * @param string $dir
-        */
-       private function teardownUploadDir( $dir ) {
-               if ( $this->keepUploads ) {
-                       return;
-               }
-
-               // delete the files first, then the dirs.
-               self::deleteFiles(
-                       [
-                               "$dir/3/3a/Foobar.jpg",
-                               "$dir/thumb/3/3a/Foobar.jpg/*.jpg",
-                               "$dir/e/ea/Thumb.png",
-                               "$dir/0/09/Bad.jpg",
-                               "$dir/5/5f/LoremIpsum.djvu",
-                               "$dir/thumb/5/5f/LoremIpsum.djvu/*-LoremIpsum.djvu.jpg",
-                               "$dir/f/ff/Foobar.svg",
-                               "$dir/thumb/f/ff/Foobar.svg/*-Foobar.svg.png",
-                               "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
-                               "$dir/0/00/Video.ogv",
-                               "$dir/thumb/0/00/Video.ogv/120px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/180px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/240px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/320px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/270px--Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/320px-seek=2-Video.ogv.jpg",
-                               "$dir/thumb/0/00/Video.ogv/320px-seek=3.3666666666667-Video.ogv.jpg",
-                               "$dir/4/41/Audio.oga",
-                       ]
-               );
-
-               self::deleteDirs(
-                       [
-                               "$dir/3/3a",
-                               "$dir/3",
-                               "$dir/thumb/3/3a/Foobar.jpg",
-                               "$dir/thumb/3/3a",
-                               "$dir/thumb/3",
-                               "$dir/e/ea",
-                               "$dir/e",
-                               "$dir/f/ff/",
-                               "$dir/f/",
-                               "$dir/thumb/f/ff/Foobar.svg",
-                               "$dir/thumb/f/ff/",
-                               "$dir/thumb/f/",
-                               "$dir/0/00/",
-                               "$dir/0/09/",
-                               "$dir/0/",
-                               "$dir/5/5f",
-                               "$dir/5",
-                               "$dir/thumb/0/00/Video.ogv",
-                               "$dir/thumb/0/00",
-                               "$dir/thumb/0",
-                               "$dir/thumb/5/5f/LoremIpsum.djvu",
-                               "$dir/thumb/5/5f",
-                               "$dir/thumb/5",
-                               "$dir/thumb",
-                               "$dir/4/41",
-                               "$dir/4",
-                               "$dir/math/f/a/5",
-                               "$dir/math/f/a",
-                               "$dir/math/f",
-                               "$dir/math",
-                               "$dir/lockdir",
-                               "$dir",
-                       ]
-               );
-       }
-
-       /**
-        * Delete the specified files, if they exist.
-        * @param array $files Full paths to files to delete.
-        */
-       private static function deleteFiles( $files ) {
-               foreach ( $files as $pattern ) {
-                       foreach ( glob( $pattern ) as $file ) {
-                               if ( file_exists( $file ) ) {
-                                       unlink( $file );
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Delete the specified directories, if they exist. Must be empty.
-        * @param array $dirs Full paths to directories to delete.
-        */
-       private static function deleteDirs( $dirs ) {
-               foreach ( $dirs as $dir ) {
-                       if ( is_dir( $dir ) ) {
-                               rmdir( $dir );
-                       }
-               }
-       }
-
-       /**
-        * "Running test $desc..."
-        * @param string $desc
-        */
-       protected function showTesting( $desc ) {
-               print "Running test $desc... ";
-       }
-
-       /**
-        * Print a happy success message.
-        *
-        * Refactored in 1.22 to use ParserTestResult
-        *
-        * @param ParserTestResult $testResult
-        * @return bool
-        */
-       protected function showSuccess( ParserTestResult $testResult ) {
-               if ( $this->showProgress ) {
-                       print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
-               }
-
-               return true;
-       }
-
-       /**
-        * Print a failure message and provide some explanatory output
-        * about what went wrong if so configured.
-        *
-        * Refactored in 1.22 to use ParserTestResult
-        *
-        * @param ParserTestResult $testResult
-        * @return bool
-        */
-       protected function showFailure( ParserTestResult $testResult ) {
-               if ( $this->showFailure ) {
-                       if ( !$this->showProgress ) {
-                               # In quiet mode we didn't show the 'Testing' message before the
-                               # test, in case it succeeded. Show it now:
-                               $this->showTesting( $testResult->description );
-                       }
-
-                       print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
-
-                       if ( $this->showOutput ) {
-                               print "--- Expected ---\n{$testResult->expected}\n";
-                               print "--- Actual ---\n{$testResult->actual}\n";
-                       }
-
-                       if ( $this->showDiffs ) {
-                               print $this->quickDiff( $testResult->expected, $testResult->actual );
-                               if ( !$this->wellFormed( $testResult->actual ) ) {
-                                       print "XML error: $this->mXmlError\n";
-                               }
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Print a skipped message.
-        *
-        * @return bool
-        */
-       protected function showSkipped() {
-               if ( $this->showProgress ) {
-                       print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
-               }
-
-               return true;
-       }
-
-       /**
-        * Run given strings through a diff and return the (colorized) output.
-        * Requires writable /tmp directory and a 'diff' command in the PATH.
-        *
-        * @param string $input
-        * @param string $output
-        * @param string $inFileTail Tailing for the input file name
-        * @param string $outFileTail Tailing for the output file name
-        * @return string
-        */
-       protected function quickDiff( $input, $output,
-               $inFileTail = 'expected', $outFileTail = 'actual'
-       ) {
-               if ( $this->markWhitespace ) {
-                       $pairs = [
-                               "\n" => '¶',
-                               ' ' => '·',
-                               "\t" => '→'
-                       ];
-                       $input = strtr( $input, $pairs );
-                       $output = strtr( $output, $pairs );
-               }
-
-               # Windows, or at least the fc utility, is retarded
-               $slash = wfIsWindows() ? '\\' : '/';
-               $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
-
-               $infile = "$prefix-$inFileTail";
-               $this->dumpToFile( $input, $infile );
-
-               $outfile = "$prefix-$outFileTail";
-               $this->dumpToFile( $output, $outfile );
-
-               $shellInfile = wfEscapeShellArg( $infile );
-               $shellOutfile = wfEscapeShellArg( $outfile );
-
-               global $wgDiff3;
-               // we assume that people with diff3 also have usual diff
-               if ( $this->useDwdiff ) {
-                       $shellCommand = 'dwdiff -Pc';
-               } else {
-                       $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
-               }
-
-               $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
-
-               unlink( $infile );
-               unlink( $outfile );
-
-               if ( $this->useDwdiff ) {
-                       return $diff;
-               } else {
-                       return $this->colorDiff( $diff );
-               }
-       }
-
-       /**
-        * Write the given string to a file, adding a final newline.
-        *
-        * @param string $data
-        * @param string $filename
-        */
-       private function dumpToFile( $data, $filename ) {
-               $file = fopen( $filename, "wt" );
-               fwrite( $file, $data . "\n" );
-               fclose( $file );
-       }
-
-       /**
-        * Colorize unified diff output if set for ANSI color output.
-        * Subtractions are colored blue, additions red.
-        *
-        * @param string $text
-        * @return string
-        */
-       protected function colorDiff( $text ) {
-               return preg_replace(
-                       [ '/^(-.*)$/m', '/^(\+.*)$/m' ],
-                       [ $this->term->color( 34 ) . '$1' . $this->term->reset(),
-                               $this->term->color( 31 ) . '$1' . $this->term->reset() ],
-                       $text );
-       }
-
-       /**
-        * Show "Reading tests from ..."
-        *
-        * @param string $path
-        */
-       public function showRunFile( $path ) {
-               print $this->term->color( 1 ) .
-                       "Reading tests from \"$path\"..." .
-                       $this->term->reset() .
-                       "\n";
-       }
-
-       /**
-        * Insert a temporary test article
-        * @param string $name The title, including any prefix
-        * @param string $text The article text
-        * @param int|string $line The input line number, for reporting errors
-        * @param bool|string $ignoreDuplicate Whether to silently ignore duplicate pages
-        * @throws Exception
-        * @throws MWException
-        */
-       public static function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
-               global $wgCapitalLinks;
-
-               $oldCapitalLinks = $wgCapitalLinks;
-               $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
-
-               $text = self::chomp( $text );
-               $name = self::chomp( $name );
-
-               $title = Title::newFromText( $name );
-
-               if ( is_null( $title ) ) {
-                       throw new MWException( "invalid title '$name' at line $line\n" );
-               }
-
-               $page = WikiPage::factory( $title );
-               $page->loadPageData( 'fromdbmaster' );
-
-               if ( $page->exists() ) {
-                       if ( $ignoreDuplicate == 'ignoreduplicate' ) {
-                               return;
-                       } else {
-                               throw new MWException( "duplicate article '$name' at line $line\n" );
-                       }
-               }
-
-               $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
-
-               $wgCapitalLinks = $oldCapitalLinks;
-       }
-
-       /**
-        * Steal a callback function from the primary parser, save it for
-        * application to our scary parser. If the hook is not installed,
-        * abort processing of this file.
-        *
-        * @param string $name
-        * @return bool True if tag hook is present
-        */
-       public function requireHook( $name ) {
-               global $wgParser;
-
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-
-               if ( isset( $wgParser->mTagHooks[$name] ) ) {
-                       $this->hooks[$name] = $wgParser->mTagHooks[$name];
-               } else {
-                       echo "   This test suite requires the '$name' hook extension, skipping.\n";
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * Steal a callback function from the primary parser, save it for
-        * application to our scary parser. If the hook is not installed,
-        * abort processing of this file.
-        *
-        * @param string $name
-        * @return bool True if function hook is present
-        */
-       public function requireFunctionHook( $name ) {
-               global $wgParser;
-
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-
-               if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
-                       $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name];
-               } else {
-                       echo "   This test suite requires the '$name' function hook extension, skipping.\n";
-                       return false;
-               }
-
-               return true;
-       }
-
-       /**
-        * Steal a callback function from the primary parser, save it for
-        * application to our scary parser. If the hook is not installed,
-        * abort processing of this file.
-        *
-        * @param string $name
-        * @return bool True if function hook is present
-        */
-       public function requireTransparentHook( $name ) {
-               global $wgParser;
-
-               $wgParser->firstCallInit(); // make sure hooks are loaded.
-
-               if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
-                       $this->transparentHooks[$name] = $wgParser->mTransparentTagHooks[$name];
-               } else {
-                       echo "   This test suite requires the '$name' transparent hook extension, skipping.\n";
-                       return false;
-               }
-
-               return true;
-       }
-
-       private function wellFormed( $text ) {
-               $html =
-                       Sanitizer::hackDocType() .
-                               '<html>' .
-                               $text .
-                               '</html>';
-
-               $parser = xml_parser_create( "UTF-8" );
-
-               # case folding violates XML standard, turn it off
-               xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-
-               if ( !xml_parse( $parser, $html, true ) ) {
-                       $err = xml_error_string( xml_get_error_code( $parser ) );
-                       $position = xml_get_current_byte_index( $parser );
-                       $fragment = $this->extractFragment( $html, $position );
-                       $this->mXmlError = "$err at byte $position:\n$fragment";
-                       xml_parser_free( $parser );
-
-                       return false;
-               }
-
-               xml_parser_free( $parser );
-
-               return true;
-       }
-
-       private function extractFragment( $text, $position ) {
-               $start = max( 0, $position - 10 );
-               $before = $position - $start;
-               $fragment = '...' .
-                       $this->term->color( 34 ) .
-                       substr( $text, $start, $before ) .
-                       $this->term->color( 0 ) .
-                       $this->term->color( 31 ) .
-                       $this->term->color( 1 ) .
-                       substr( $text, $position, 1 ) .
-                       $this->term->color( 0 ) .
-                       $this->term->color( 34 ) .
-                       substr( $text, $position + 1, 9 ) .
-                       $this->term->color( 0 ) .
-                       '...';
-               $display = str_replace( "\n", ' ', $fragment );
-               $caret = '   ' .
-                       str_repeat( ' ', $before ) .
-                       $this->term->color( 31 ) .
-                       '^' .
-                       $this->term->color( 0 );
-
-               return "$display\n$caret";
-       }
-
-       static function getFakeTimestamp( &$parser, &$ts ) {
-               $ts = 123; // parsed as '1970-01-01T00:02:03Z'
-               return true;
-       }
-}
-
-class ParserTestResultNormalizer {
-       protected $doc, $xpath, $invalid;
-
-       public static function normalize( $text, $funcs ) {
-               $norm = new self( $text );
-               if ( $norm->invalid ) {
-                       return $text;
-               }
-               foreach ( $funcs as $func ) {
-                       $norm->$func();
-               }
-               return $norm->serialize();
-       }
-
-       protected function __construct( $text ) {
-               $this->doc = new DOMDocument( '1.0', 'utf-8' );
-
-               // Note: parsing a supposedly XHTML document with an XML parser is not
-               // guaranteed to give accurate results. For example, it may introduce
-               // differences in the number of line breaks in <pre> tags.
-
-               MediaWiki\suppressWarnings();
-               if ( !$this->doc->loadXML( '<html><body>' . $text . '</body></html>' ) ) {
-                       $this->invalid = true;
-               }
-               MediaWiki\restoreWarnings();
-               $this->xpath = new DOMXPath( $this->doc );
-               $this->body = $this->xpath->query( '//body' )->item( 0 );
-       }
-
-       protected function removeTbody() {
-               foreach ( $this->xpath->query( '//tbody' ) as $tbody ) {
-                       while ( $tbody->firstChild ) {
-                               $child = $tbody->firstChild;
-                               $tbody->removeChild( $child );
-                               $tbody->parentNode->insertBefore( $child, $tbody );
-                       }
-                       $tbody->parentNode->removeChild( $tbody );
-               }
-       }
-
-       /**
-        * The point of this function is to produce a normalized DOM in which
-        * Tidy's output matches the output of html5depurate. Tidy both trims
-        * and pretty-prints, so this requires fairly aggressive treatment.
-        *
-        * In particular, note that Tidy converts <pre>x</pre> to <pre>\nx\n</pre>,
-        * which theoretically affects display since the second line break is not
-        * ignored by compliant HTML parsers.
-        *
-        * This function also removes empty elements, as does Tidy.
-        */
-       protected function trimWhitespace() {
-               foreach ( $this->xpath->query( '//text()' ) as $child ) {
-                       if ( strtolower( $child->parentNode->nodeName ) === 'pre' ) {
-                               // Just trim one line break from the start and end
-                               if ( substr_compare( $child->data, "\n", 0 ) === 0 ) {
-                                       $child->data = substr( $child->data, 1 );
-                               }
-                               if ( substr_compare( $child->data, "\n", -1 ) === 0 ) {
-                                       $child->data = substr( $child->data, 0, -1 );
-                               }
-                       } else {
-                               // Trim all whitespace
-                               $child->data = trim( $child->data );
-                       }
-                       if ( $child->data === '' ) {
-                               $child->parentNode->removeChild( $child );
-                       }
-               }
-       }
-
-       /**
-        * Serialize the XML DOM for comparison purposes. This does not generate HTML.
-        */
-       protected function serialize() {
-               return strtr( $this->doc->saveXML( $this->body ),
-                       [ '<body>' => '', '</body>' => '' ] );
-       }
-}
diff --git a/tests/parser/parserTestsParserHook.php b/tests/parser/parserTestsParserHook.php
deleted file mode 100644 (file)
index 5bf50ea..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/**
- * A basic extension that's used by the parser tests to test whether input and
- * arguments are passed to extensions properly.
- *
- * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason
- *
- * 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 Testing
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-class ParserTestParserHook {
-
-       static function setup( &$parser ) {
-               $parser->setHook( 'tag', [ __CLASS__, 'dumpHook' ] );
-               $parser->setHook( 'tåg', [ __CLASS__, 'dumpHook' ] );
-               $parser->setHook( 'statictag', [ __CLASS__, 'staticTagHook' ] );
-               return true;
-       }
-
-       static function dumpHook( $in, $argv ) {
-               return "<pre>\n" .
-                       var_export( $in, true ) . "\n" .
-                       var_export( $argv, true ) . "\n" .
-                       "</pre>";
-       }
-
-       static function staticTagHook( $in, $argv, $parser ) {
-               if ( !count( $argv ) ) {
-                       $parser->static_tag_buf = $in;
-                       return '';
-               } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
-                       && $argv['action'] === 'flush' && $in === null
-               ) {
-                       // Clear the buffer, we probably don't need to
-                       if ( isset( $parser->static_tag_buf ) ) {
-                               $tmp = $parser->static_tag_buf;
-                       } else {
-                               $tmp = '';
-                       }
-                       $parser->static_tag_buf = null;
-                       return $tmp;
-               } else { // wtf?
-                       return
-                               "\nCall this extension as <statictag>string</statictag> or as" .
-                               " <statictag action=flush/>, not in any other way.\n" .
-                               "text: " . var_export( $in, true ) . "\n" .
-                               "argv: " . var_export( $argv, true ) . "\n";
-               }
-       }
-}
index f961dd4..915eac6 100644 (file)
@@ -52,8 +52,6 @@ Options:
   --setversion     When using --record, set the version string to use (useful
                    with git-svn so that you can get the exact revision)
   --keep-uploads   Re-use the same upload directory for each test, don't delete it
-  --fuzz           Do a fuzz test instead of a normal test
-  --seed <n>       Start the fuzz test from the specified seed
   --run-disabled   run disabled tests
   --run-parsoid    run parsoid tests (normally disabled)
   --dwdiff         Use dwdiff to display diff output
@@ -94,9 +92,5 @@ if ( isset( $options['file'] ) ) {
 $version = SpecialVersion::getVersion( 'nodb' );
 echo "This is MediaWiki version {$version}.\n\n";
 
-if ( isset( $options['fuzz'] ) ) {
-       $tester->fuzzTest( $files );
-} else {
-       $ok = $tester->runTestsFromFiles( $files );
-       exit( $ok ? 0 : 1 );
-}
+$ok = $tester->runTestsFromFiles( $files );
+exit( $ok ? 0 : 1 );
index e1537bf..8503393 100644 (file)
@@ -76,7 +76,7 @@ help:
        #   tap                 Run the tests individually through Test::Harness's prove(1)
        #   help                You're looking at it!
        #   coverage            Run the tests and generates an HTML code coverage report
-       #                       You will need the Xdebug PHP extension for the later.
+       #                       You will need the Xdebug PHP extension for the latter.
        #   [no]parser          Skip or only run Parser tests
        #
        #   list-groups         List available Tests groups.
index 541ac11..50b3390 100644 (file)
@@ -477,6 +477,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                        while ( $this->db->trxLevel() > 0 ) {
                                $this->db->rollback( __METHOD__, 'flush' );
                        }
+                       // Check for unsafe queries
+                       if ( $this->db->getType() === 'mysql' ) {
+                               $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'" );
+                       }
                }
 
                DeferredUpdates::clearPendingUpdates();
@@ -495,7 +499,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
        }
 
        protected function tearDown() {
-               global $wgRequest;
+               global $wgRequest, $wgSQLMode;
 
                $status = ob_get_status();
                if ( isset( $status['name'] ) &&
@@ -519,6 +523,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                        while ( $this->db->trxLevel() > 0 ) {
                                $this->db->rollback( __METHOD__, 'flush' );
                        }
+                       if ( $this->db->getType() === 'mysql' ) {
+                               $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ) );
+                       }
                }
 
                // Restore mw globals
index 4622a38..e3713ab 100644 (file)
@@ -1,8 +1,162 @@
 <?php
 
-class FauxRequestTest extends MediaWikiTestCase {
+class FauxRequestTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @covers FauxRequest::__construct
+        */
+       public function testConstructInvalidData() {
+               $this->setExpectedException( MWException::class, 'bogus data' );
+               $req = new FauxRequest( 'x' );
+       }
+
+       /**
+        * @covers FauxRequest::__construct
+        */
+       public function testConstructInvalidSession() {
+               $this->setExpectedException( MWException::class, 'bogus session' );
+               $req = new FauxRequest( [], false, 'x' );
+       }
+
+       /**
+        * @covers FauxRequest::getText
+        */
+       public function testGetText() {
+               $req = new FauxRequest( [ 'x' => 'Value' ] );
+               $this->assertEquals( 'Value', $req->getText( 'x' ) );
+               $this->assertEquals( '', $req->getText( 'z' ) );
+       }
+
+       // Integration test for parent method.
+       public function testGetVal() {
+               $req = new FauxRequest( [ 'crlf' => "A\r\nb" ] );
+               $this->assertSame( "A\r\nb", $req->getVal( 'crlf' ), 'CRLF' );
+       }
+
+       // Integration test for parent method.
+       public function testGetRawVal() {
+               $req = new FauxRequest( [
+                       'x' => 'Value',
+                       'y' => [ 'a' ],
+                       'crlf' => "A\r\nb"
+               ] );
+               $this->assertSame( 'Value', $req->getRawVal( 'x' ) );
+               $this->assertSame( null, $req->getRawVal( 'z' ), 'Not found' );
+               $this->assertSame( null, $req->getRawVal( 'y' ), 'Array is ignored' );
+               $this->assertSame( "A\r\nb", $req->getRawVal( 'crlf' ), 'CRLF' );
+       }
+
+       /**
+        * @covers FauxRequest::getValues
+        */
+       public function testGetValues() {
+               $values = [ 'x' => 'Value', 'y' => '' ];
+               $req = new FauxRequest( $values );
+               $this->assertEquals( $values, $req->getValues() );
+       }
+
+       /**
+        * @covers FauxRequest::getQueryValues
+        */
+       public function testGetQueryValues() {
+               $values = [ 'x' => 'Value', 'y' => '' ];
+
+               $req = new FauxRequest( $values );
+               $this->assertEquals( $values, $req->getQueryValues() );
+               $req = new FauxRequest( $values, /*wasPosted*/ true );
+               $this->assertEquals( [], $req->getQueryValues() );
+       }
+
+       /**
+        * @covers FauxRequest::getMethod
+        */
+       public function testGetMethod() {
+               $req = new FauxRequest( [] );
+               $this->assertEquals( 'GET', $req->getMethod() );
+               $req = new FauxRequest( [], /*wasPosted*/ true );
+               $this->assertEquals( 'POST', $req->getMethod() );
+       }
+
+       /**
+        * @covers FauxRequest::wasPosted
+        */
+       public function testWasPosted() {
+               $req = new FauxRequest( [] );
+               $this->assertFalse( $req->wasPosted() );
+               $req = new FauxRequest( [], /*wasPosted*/ true );
+               $this->assertTrue( $req->wasPosted() );
+       }
+
+       /**
+        * @covers FauxRequest::getCookie
+        * @covers FauxRequest::setCookie
+        * @covers FauxRequest::setCookies
+        */
+       public function testCookies() {
+               $req = new FauxRequest();
+               $this->assertSame( null, $req->getCookie( 'z', '' ) );
+
+               $req->setCookie( 'x', 'Value', '' );
+               $this->assertEquals( 'Value', $req->getCookie( 'x', '' ) );
+
+               $req->setCookies( [ 'x' => 'One', 'y' => 'Two' ], '' );
+               $this->assertEquals( 'One', $req->getCookie( 'x', '' ) );
+               $this->assertEquals( 'Two', $req->getCookie( 'y', '' ) );
+       }
+
+       /**
+        * @covers FauxRequest::getCookie
+        * @covers FauxRequest::setCookie
+        * @covers FauxRequest::setCookies
+        */
+       public function testCookiesDefaultPrefix() {
+               global $wgCookiePrefix;
+               $oldPrefix = $wgCookiePrefix;
+               $wgCookiePrefix = '_';
+
+               $req = new FauxRequest();
+               $this->assertSame( null, $req->getCookie( 'z' ) );
+
+               $req->setCookie( 'x', 'Value' );
+               $this->assertEquals( 'Value', $req->getCookie( 'x' ) );
+
+               $wgCookiePrefix = $oldPrefix;
+       }
+
+       /**
+        * @covers FauxRequest::getRequestURL
+        */
+       public function testGetRequestURL() {
+               $req = new FauxRequest();
+               $this->setExpectedException( MWException::class );
+               $req->getRequestURL();
+       }
+
+       /**
+        * @covers FauxRequest::setRequestURL
+        * @covers FauxRequest::getRequestURL
+        */
+       public function testSetRequestURL() {
+               $req = new FauxRequest();
+               $req->setRequestURL( 'https://example.org' );
+               $this->assertEquals( 'https://example.org', $req->getRequestURL() );
+       }
+
+       /**
+        * @covers FauxRequest::__construct
+        * @covers FauxRequest::getProtocol
+        */
+       public function testProtocol() {
+               $req = new FauxRequest();
+               $this->assertEquals( 'http', $req->getProtocol() );
+               $req = new FauxRequest( [], false, null, 'http' );
+               $this->assertEquals( 'http', $req->getProtocol() );
+               $req = new FauxRequest( [], false, null, 'https' );
+               $this->assertEquals( 'https', $req->getProtocol() );
+       }
+
        /**
         * @covers FauxRequest::setHeader
+        * @covers FauxRequest::setHeaders
         * @covers FauxRequest::getHeader
         */
        public function testGetSetHeader() {
@@ -22,7 +176,7 @@ class FauxRequestTest extends MediaWikiTestCase {
        }
 
        /**
-        * @covers FauxRequest::getAllHeaders
+        * @covers FauxRequest::initHeaders
         */
        public function testGetAllHeaders() {
                $_SERVER['HTTP_TEST'] = 'Example';
@@ -33,19 +187,36 @@ class FauxRequestTest extends MediaWikiTestCase {
                        [],
                        $request->getAllHeaders()
                );
+
+               $this->assertEquals(
+                       false,
+                       $request->getHeader( 'test' )
+               );
        }
 
        /**
-        * @covers FauxRequest::getHeader
+        * @covers FauxRequest::__construct
+        * @covers FauxRequest::getSessionArray
         */
-       public function testGetHeader() {
-               $_SERVER['HTTP_TEST'] = 'Example';
+       public function testSessionData() {
+               $values = [ 'x' => 'Value', 'y' => '' ];
 
-               $request = new FauxRequest();
+               $req = new FauxRequest( [], false, /*session*/ $values );
+               $this->assertEquals( $values, $req->getSessionArray() );
 
-               $this->assertEquals(
-                       false,
-                       $request->getHeader( 'test' )
-               );
+               $req = new FauxRequest();
+               $this->assertSame( null, $req->getSessionArray() );
+       }
+
+       /**
+        * @covers FauxRequest::getRawQueryString
+        * @covers FauxRequest::getRawPostString
+        * @covers FauxRequest::getRawInput
+        */
+       public function testDummies() {
+               $req = new FauxRequest();
+               $this->assertEquals( '', $req->getRawQueryString() );
+               $this->assertEquals( '', $req->getRawPostString() );
+               $this->assertEquals( '', $req->getRawInput() );
        }
 }
index e44db83..85c95e4 100644 (file)
@@ -271,7 +271,7 @@ class HtmlTest extends MediaWikiTestCase {
        /**
         * How do we handle duplicate keys in HTML attributes expansion?
         * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false )
-        * The later will take precedence.
+        * The latter will take precedence.
         *
         * Feature added by r96188
         * @covers Html::expandAttributes
index c946689..aade490 100644 (file)
@@ -23,8 +23,11 @@ class WebRequestTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideDetectServer
         * @covers WebRequest::detectServer
+        * @covers WebRequest::detectProtocol
         */
        public function testDetectServer( $expected, $input, $description ) {
+               $this->setMwGlobals( 'wgAssumeProxiesUseDefaultProtocolPorts', true );
+
                $_SERVER = $input;
                $result = WebRequest::detectServer();
                $this->assertEquals( $expected, $result, $description );
@@ -63,6 +66,24 @@ class WebRequestTest extends MediaWikiTestCase {
                                ],
                                'Secure off'
                        ],
+                       [
+                               'https://x',
+                               [
+                                       'HTTP_HOST' => 'x',
+                                       'HTTP_X_FORWARDED_PROTO' => 'https',
+                               ],
+                               'Forwarded HTTPS'
+                       ],
+                       [
+                               'https://x',
+                               [
+                                       'HTTP_HOST' => 'x',
+                                       'HTTPS' => 'off',
+                                       'SERVER_PORT' => '81',
+                                       'HTTP_X_FORWARDED_PROTO' => 'https',
+                               ],
+                               'Forwarded HTTPS'
+                       ],
                        [
                                'http://y',
                                [
@@ -104,6 +125,241 @@ class WebRequestTest extends MediaWikiTestCase {
                ];
        }
 
+       protected function mockWebRequest( $data = [] ) {
+               // Cannot use PHPUnit getMockBuilder() as it does not support
+               // overriding protected properties afterwards
+               $reflection = new ReflectionClass( 'WebRequest' );
+               $req = $reflection->newInstanceWithoutConstructor();
+
+               $prop = $reflection->getProperty( 'data' );
+               $prop->setAccessible( true );
+               $prop->setValue( $req, $data );
+
+               $prop = $reflection->getProperty( 'requestTime' );
+               $prop->setAccessible( true );
+               $prop->setValue( $req, microtime( true ) );
+
+               return $req;
+       }
+
+       /**
+        * @covers WebRequest::getElapsedTime
+        */
+       public function testGetElapsedTime() {
+               $req = $this->mockWebRequest();
+               $this->assertGreaterThanOrEqual( 0.0, $req->getElapsedTime() );
+               $this->assertEquals( 0.0, $req->getElapsedTime(), '', /*delta*/ 0.2 );
+       }
+
+       /**
+        * @covers WebRequest::getVal
+        * @covers WebRequest::getGPCVal
+        * @covers WebRequest::normalizeUnicode
+        */
+       public function testGetValNormal() {
+               // Assert that WebRequest normalises GPC data using UtfNormal\Validator
+               $input = "a \x00 null";
+               $normal = "a \xef\xbf\xbd null";
+               $req = $this->mockWebRequest( [ 'x' => $input, 'y' => [ $input, $input ] ] );
+               $this->assertSame( $normal, $req->getVal( 'x' ) );
+               $this->assertNotSame( $input, $req->getVal( 'x' ) );
+               $this->assertSame( [ $normal, $normal ], $req->getArray( 'y' ) );
+       }
+
+       /**
+        * @covers WebRequest::getVal
+        * @covers WebRequest::getGPCVal
+        */
+       public function testGetVal() {
+               $req = $this->mockWebRequest( [ 'x' => 'Value', 'y' => [ 'a' ], 'crlf' => "A\r\nb" ] );
+               $this->assertSame( 'Value', $req->getVal( 'x' ), 'Simple value' );
+               $this->assertSame( null, $req->getVal( 'z' ), 'Not found' );
+               $this->assertSame( null, $req->getVal( 'y' ), 'Array is ignored' );
+               $this->assertSame( "A\r\nb", $req->getVal( 'crlf' ), 'CRLF' );
+       }
+
+       /**
+        * @covers WebRequest::getRawVal
+        */
+       public function testGetRawVal() {
+               $req = $this->mockWebRequest( [
+                       'x' => 'Value',
+                       'y' => [ 'a' ],
+                       'crlf' => "A\r\nb"
+               ] );
+               $this->assertSame( 'Value', $req->getRawVal( 'x' ) );
+               $this->assertSame( null, $req->getRawVal( 'z' ), 'Not found' );
+               $this->assertSame( null, $req->getRawVal( 'y' ), 'Array is ignored' );
+               $this->assertSame( "A\r\nb", $req->getRawVal( 'crlf' ), 'CRLF' );
+       }
+
+       /**
+        * @covers WebRequest::getArray
+        */
+       public function testGetArray() {
+               $req = $this->mockWebRequest( [ 'x' => 'Value', 'y' => [ 'a', 'b' ] ] );
+               $this->assertSame( [ 'Value' ], $req->getArray( 'x' ), 'Value becomes array' );
+               $this->assertSame( null, $req->getArray( 'z' ), 'Not found' );
+               $this->assertSame( [ 'a', 'b' ], $req->getArray( 'y' ) );
+       }
+
+       /**
+        * @covers WebRequest::getIntArray
+        */
+       public function testGetIntArray() {
+               $req = $this->mockWebRequest( [ 'x' => [ 'Value' ], 'y' => [ '0', '4.2', '-2' ] ] );
+               $this->assertSame( [ 0 ], $req->getIntArray( 'x' ), 'Text becomes 0' );
+               $this->assertSame( null, $req->getIntArray( 'z' ), 'Not found' );
+               $this->assertSame( [ 0, 4, -2 ], $req->getIntArray( 'y' ) );
+       }
+
+       /**
+        * @covers WebRequest::getInt
+        */
+       public function testGetInt() {
+               $req = $this->mockWebRequest( [
+                       'x' => 'Value',
+                       'y' => [ 'a' ],
+                       'zero' => '0',
+                       'answer' => '4.2',
+                       'neg' => '-2',
+               ] );
+               $this->assertSame( 0, $req->getInt( 'x' ), 'Text' );
+               $this->assertSame( 0, $req->getInt( 'y' ), 'Array' );
+               $this->assertSame( 0, $req->getInt( 'z' ), 'Not found' );
+               $this->assertSame( 0, $req->getInt( 'zero' ) );
+               $this->assertSame( 4, $req->getInt( 'answer' ) );
+               $this->assertSame( -2, $req->getInt( 'neg' ) );
+       }
+
+       /**
+        * @covers WebRequest::getIntOrNull
+        */
+       public function testGetIntOrNull() {
+               $req = $this->mockWebRequest( [
+                       'x' => 'Value',
+                       'y' => [ 'a' ],
+                       'zero' => '0',
+                       'answer' => '4.2',
+                       'neg' => '-2',
+               ] );
+               $this->assertSame( null, $req->getIntOrNull( 'x' ), 'Text' );
+               $this->assertSame( null, $req->getIntOrNull( 'y' ), 'Array' );
+               $this->assertSame( null, $req->getIntOrNull( 'z' ), 'Not found' );
+               $this->assertSame( 0, $req->getIntOrNull( 'zero' ) );
+               $this->assertSame( 4, $req->getIntOrNull( 'answer' ) );
+               $this->assertSame( -2, $req->getIntOrNull( 'neg' ) );
+       }
+
+       /**
+        * @covers WebRequest::getFloat
+        */
+       public function testGetFloat() {
+               $req = $this->mockWebRequest( [
+                       'x' => 'Value',
+                       'y' => [ 'a' ],
+                       'zero' => '0',
+                       'answer' => '4.2',
+                       'neg' => '-2',
+               ] );
+               $this->assertSame( 0.0, $req->getFloat( 'x' ), 'Text' );
+               $this->assertSame( 0.0, $req->getFloat( 'y' ), 'Array' );
+               $this->assertSame( 0.0, $req->getFloat( 'z' ), 'Not found' );
+               $this->assertSame( 0.0, $req->getFloat( 'zero' ) );
+               $this->assertSame( 4.2, $req->getFloat( 'answer' ) );
+               $this->assertSame( -2.0, $req->getFloat( 'neg' ) );
+       }
+
+       /**
+        * @covers WebRequest::getBool
+        */
+       public function testGetBool() {
+               $req = $this->mockWebRequest( [
+                       'x' => 'Value',
+                       'y' => [ 'a' ],
+                       'zero' => '0',
+                       'f' => 'false',
+                       't' => 'true',
+               ] );
+               $this->assertSame( true, $req->getBool( 'x' ), 'Text' );
+               $this->assertSame( false, $req->getBool( 'y' ), 'Array' );
+               $this->assertSame( false, $req->getBool( 'z' ), 'Not found' );
+               $this->assertSame( false, $req->getBool( 'zero' ) );
+               $this->assertSame( true, $req->getBool( 'f' ) );
+               $this->assertSame( true, $req->getBool( 't' ) );
+       }
+
+       public static function provideFuzzyBool() {
+               return [
+                       [ 'Text', true ],
+                       [ '', false, '(empty string)' ],
+                       [ '0', false ],
+                       [ '1', true ],
+                       [ 'false', false ],
+                       [ 'true', true ],
+                       [ 'False', false ],
+                       [ 'True', true ],
+                       [ 'FALSE', false ],
+                       [ 'TRUE', true ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideFuzzyBool
+        * @covers WebRequest::getFuzzyBool
+        */
+       public function testGetFuzzyBool( $value, $expected, $message = null ) {
+               $req = $this->mockWebRequest( [ 'x' => $value ] );
+               $this->assertSame( $expected, $req->getFuzzyBool( 'x' ), $message ?: "Value: '$value'" );
+       }
+
+       /**
+        * @covers WebRequest::getFuzzyBool
+        */
+       public function testGetFuzzyBoolDefault() {
+               $req = $this->mockWebRequest();
+               $this->assertSame( false, $req->getFuzzyBool( 'z' ), 'Not found' );
+       }
+
+       /**
+        * @covers WebRequest::getCheck
+        */
+       public function testGetCheck() {
+               $req = $this->mockWebRequest( [ 'x' => 'Value', 'zero' => '0' ] );
+               $this->assertSame( false, $req->getCheck( 'z' ), 'Not found' );
+               $this->assertSame( true, $req->getCheck( 'x' ), 'Text' );
+               $this->assertSame( true, $req->getCheck( 'zero' ) );
+       }
+
+       /**
+        * @covers WebRequest::getText
+        */
+       public function testGetText() {
+               // Avoid FauxRequest (overrides getText)
+               $req = $this->mockWebRequest( [ 'crlf' => "Va\r\nlue" ] );
+               $this->assertSame( "Va\nlue", $req->getText( 'crlf' ), 'CR stripped' );
+       }
+
+       /**
+        * @covers WebRequest::getValues
+        */
+       public function testGetValues() {
+               $values = [ 'x' => 'Value', 'y' => '' ];
+               // Avoid FauxRequest (overrides getValues)
+               $req = $this->mockWebRequest( $values );
+               $this->assertSame( $values, $req->getValues() );
+               $this->assertSame( [ 'x' => 'Value' ], $req->getValues( 'x' ), 'Specific keys' );
+       }
+
+       /**
+        * @covers WebRequest::getValueNames
+        */
+       public function testGetValueNames() {
+               $req = $this->mockWebRequest( [ 'x' => 'Value', 'y' => '' ] );
+               $this->assertSame( [ 'x', 'y' ], $req->getValueNames() );
+               $this->assertSame( [ 'x' ], $req->getValueNames( [ 'y' ] ), 'Exclude keys' );
+       }
+
        /**
         * @dataProvider provideGetIP
         * @covers WebRequest::getIP
@@ -343,6 +599,7 @@ class WebRequestTest extends MediaWikiTestCase {
                                [ 'en-gb' => 1, 'en-us' => '1' ],
                                'Two equally prefered English variants'
                        ],
+                       [ '_', [], 'Invalid input' ],
                ];
        }
 
index 7a8e208..02d0a0d 100644 (file)
@@ -513,4 +513,57 @@ class ApiEditPageTest extends ApiTestCase {
                $this->assertEquals( "testing-nontext", $page->getContentModel() );
                $this->assertEquals( $data, $page->getContent()->serialize() );
        }
+
+       /**
+        * This test verifies that after changing the content model
+        * of a page, undoing that edit via the API will also
+        * undo the content model change.
+        */
+       public function testUndoAfterContentModelChange() {
+               $name = 'Help:' . __FUNCTION__;
+               $uploader = self::$users['uploader']->getUser();
+               $sysop = self::$users['sysop']->getUser();
+               $apiResult = $this->doApiRequestWithToken( [
+                       'action' => 'edit',
+                       'title' => $name,
+                       'text' => 'some text',
+               ], null, $sysop )[0];
+
+               // Check success
+               $this->assertArrayHasKey( 'edit', $apiResult );
+               $this->assertArrayHasKey( 'result', $apiResult['edit'] );
+               $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+               $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
+               // Content model is wikitext
+               $this->assertEquals( 'wikitext', $apiResult['edit']['contentmodel'] );
+
+               // Convert the page to JSON
+               $apiResult = $this->doApiRequestWithToken( [
+                       'action' => 'edit',
+                       'title' => $name,
+                       'text' => '{}',
+                       'contentmodel' => 'json',
+               ], null, $uploader )[0];
+
+               // Check success
+               $this->assertArrayHasKey( 'edit', $apiResult );
+               $this->assertArrayHasKey( 'result', $apiResult['edit'] );
+               $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+               $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
+               $this->assertEquals( 'json', $apiResult['edit']['contentmodel'] );
+
+               $apiResult = $this->doApiRequestWithToken( [
+                       'action' => 'edit',
+                       'title' => $name,
+                       'undo' => $apiResult['edit']['newrevid']
+               ], null, $sysop )[0];
+
+               // Check success
+               $this->assertArrayHasKey( 'edit', $apiResult );
+               $this->assertArrayHasKey( 'result', $apiResult['edit'] );
+               $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+               $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
+               // Check that the contentmodel is back to wikitext now.
+               $this->assertEquals( 'wikitext', $apiResult['edit']['contentmodel'] );
+       }
 }
index 788d304..f679f63 100644 (file)
@@ -2709,9 +2709,11 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $session->clear();
                $user = $this->getMock( 'User', [ 'addToDatabase' ] );
                $user->expects( $this->once() )->method( 'addToDatabase' )
-                       ->will( $this->returnCallback( function () use ( $username ) {
-                               $status = \User::newFromName( $username )->addToDatabase();
+                       ->will( $this->returnCallback( function () use ( $username, &$user ) {
+                               $oldUser = \User::newFromName( $username );
+                               $status = $oldUser->addToDatabase();
                                $this->assertTrue( $status->isOK(), 'sanity check' );
+                               $user->setId( $oldUser->getId() );
                                return \Status::newFatal( 'userexists' );
                        } ) );
                $user->setName( $username );
diff --git a/tests/phpunit/includes/content/JsonContentHandlerTest.php b/tests/phpunit/includes/content/JsonContentHandlerTest.php
new file mode 100644 (file)
index 0000000..abfb673
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+class JsonContentHandlerTest extends MediaWikiTestCase {
+
+       /**
+        * @covers JsonContentHandler::makeEmptyContent
+        */
+       public function testMakeEmptyContent() {
+               $handler = new JsonContentHandler();
+               $content = $handler->makeEmptyContent();
+               $this->assertInstanceOf( JsonContent::class, $content );
+               $this->assertTrue( $content->isValid() );
+       }
+}
index 16297ad..0f9a401 100644 (file)
@@ -324,18 +324,18 @@ class DatabaseTest extends MediaWikiTestCase {
        }
 
        /**
-        * @covers DatabaseBase::clearSnapshot()
+        * @covers DatabaseBase::flushSnapshot()
         */
-       public function testClearSnapshot() {
+       public function testFlushSnapshot() {
                $db = $this->db;
 
-               $db->clearSnapshot( __METHOD__ ); // ok
-               $db->clearSnapshot( __METHOD__ ); // ok
+               $db->flushSnapshot( __METHOD__ ); // ok
+               $db->flushSnapshot( __METHOD__ ); // ok
 
                $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
                $db->query( 'SELECT 1', __METHOD__ );
                $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." );
-               $db->clearSnapshot( __METHOD__ ); // ok
+               $db->flushSnapshot( __METHOD__ ); // ok
                $db->restoreFlags( $db::RESTORE_PRIOR );
 
                $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
index 3562ed8..24c5d92 100644 (file)
@@ -107,7 +107,7 @@ class LBFactoryTest extends MediaWikiTestCase {
                        $wgDBserver, $dbw->getLBInfo( 'clusterMasterHost' ), 'cluster master set' );
 
                $dbr = $lb->getConnection( DB_SLAVE );
-               $this->assertTrue( $dbr->getLBInfo( 'slave' ), 'slave shows as slave' );
+               $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
                $this->assertEquals(
                        $wgDBserver, $dbr->getLBInfo( 'clusterMasterHost' ), 'cluster master set' );
 
@@ -145,7 +145,7 @@ class LBFactoryTest extends MediaWikiTestCase {
                $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
 
                $dbr = $lb->getConnection( DB_SLAVE );
-               $this->assertTrue( $dbr->getLBInfo( 'slave' ), 'slave shows as slave' );
+               $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
 
                $factory->shutdown();
                $lb->closeAll();
index c5fd369..6520610 100644 (file)
@@ -206,7 +206,7 @@ class FileTest extends MediaWikiMediaTestCase {
                        ] ],
                        [ [
                                'supportsBucketing' => true,
-                               'tmpBucketedThumbCache' => [ 1024 => '/tmp/shouldnotexist' + rand() ],
+                               'tmpBucketedThumbCache' => [ 1024 => '/tmp/shouldnotexist' . rand() ],
                                'thumbnailBucket' => 1024,
                                'physicalWidth' => 2048,
                                'expectedPath' => 'fsFilePath',
index 1ebe551..9a48930 100644 (file)
@@ -32,12 +32,35 @@ class SamplingStatsdClientTest extends PHPUnit_Framework_TestCase {
 
                return [
                        // $data, $sampleRate, $seed, $expectWrite
-                       [ $unsampled, 1, 0 /*0.44*/, $unsampled ],
-                       [ $sampled, 1, 0 /*0.44*/, null ],
-                       [ $sampled, 1, 4 /*0.03*/, $sampled ],
-                       [ $unsampled, 0.1, 4 /*0.03*/, $sampled ],
-                       [ $sampled, 0.5, 0 /*0.44*/, null ],
-                       [ $sampled, 0.5, 4 /*0.03*/, $sampled ],
+                       [ $unsampled, 1, 0 /*0.44*/, true ],
+                       [ $sampled, 1, 0 /*0.44*/, false ],
+                       [ $sampled, 1, 4 /*0.03*/, true ],
+                       [ $unsampled, 0.1, 0 /*0.44*/, false ],
+                       [ $sampled, 0.5, 0 /*0.44*/, false ],
+                       [ $sampled, 0.5, 4 /*0.03*/, false ],
                ];
        }
+
+       public function testSetSamplingRates() {
+               $matching = new StatsdData();
+               $matching->setKey( 'foo.bar' );
+               $matching->setValue( 1 );
+
+               $nonMatching = new StatsdData();
+               $nonMatching->setKey( 'oof.bar' );
+               $nonMatching->setValue( 1 );
+
+               $sender = $this->getMock( 'Liuggio\StatsdClient\Sender\SenderInterface' );
+               $sender->expects( $this->any() )->method( 'open' )->will( $this->returnValue( true ) );
+               $sender->expects( $this->once() )->method( 'write' )->with( $this->anything(),
+                       $this->equalTo( $nonMatching ) );
+
+               $client = new SamplingStatsdClient( $sender );
+               $client->setSamplingRates( [ 'foo.*' => 0.2 ] );
+
+               mt_srand( 0 ); // next random is 0.44
+               $client->send( $matching );
+               mt_srand( 0 );
+               $client->send( $nonMatching );
+       }
 }
index b75adca..9ce93d6 100644 (file)
@@ -102,6 +102,31 @@ class WaitConditionLoopTest extends PHPUnit_Framework_TestCase {
                $loop->setWallClock( $wallClock );
                $this->assertEquals( $loop::CONDITION_TIMED_OUT, $loop->invoke() );
                $this->assertEquals( [ 1, 1, 1 ], [ $x, $y, $z ], "Busy work done" );
+
+               $loop = new WaitConditionLoopFakeTime(
+                       function () use ( &$count, &$wallClock ) {
+                               $wallClock += 3;
+                               ++$count;
+
+                               return true;
+                       },
+                       0.0,
+                       $this->newBusyWork( $x, $y, $z, $wallClock )
+               );
+               $this->assertEquals( $loop::CONDITION_REACHED, $loop->invoke() );
+
+               $count = 0;
+               $loop = new WaitConditionLoopFakeTime(
+                       function () use ( &$count, &$wallClock ) {
+                               $wallClock += 3;
+                               ++$count;
+
+                               return $count > 10 ? true : false;
+                       },
+                       0,
+                       $this->newBusyWork( $x, $y, $z, $wallClock )
+               );
+               $this->assertEquals( $loop::CONDITION_FAILED, $loop->invoke() );
        }
 
        public function testCallbackAborted() {
index aeb4666..99b959b 100644 (file)
@@ -105,6 +105,47 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $this->assertFalse( $this->cache->get( $key ), "Stale set() value ignored" );
        }
 
+       public function testProcessCache() {
+               $hit = 0;
+               $callback = function () use ( &$hit ) {
+                       ++$hit;
+                       return 42;
+               };
+               $keys = [ wfRandomString(), wfRandomString(), wfRandomString() ];
+               $groups = [ 'thiscache:1', 'thatcache:1', 'somecache:1' ];
+
+               foreach ( $keys as $i => $key ) {
+                       $this->cache->getWithSetCallback(
+                               $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+               }
+               $this->assertEquals( 3, $hit );
+
+               foreach ( $keys as $i => $key ) {
+                       $this->cache->getWithSetCallback(
+                               $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+               }
+               $this->assertEquals( 3, $hit, "Values cached" );
+
+               foreach ( $keys as $i => $key ) {
+                       $this->cache->getWithSetCallback(
+                               "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+               }
+               $this->assertEquals( 6, $hit );
+
+               foreach ( $keys as $i => $key ) {
+                       $this->cache->getWithSetCallback(
+                               "$key-2", 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+               }
+               $this->assertEquals( 6, $hit, "New values cached" );
+
+               foreach ( $keys as $i => $key ) {
+                       $this->cache->delete( $key );
+                       $this->cache->getWithSetCallback(
+                               $key, 100, $callback, [ 'pcTTL' => 5, 'pcGroup' => $groups[$i] ] );
+               }
+               $this->assertEquals( 9, $hit, "Values evicted" );
+       }
+
        /**
         * @dataProvider getWithSetCallback_provider
         * @covers WANObjectCache::getWithSetCallback()
@@ -120,9 +161,15 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $cKey1 = wfRandomString();
                $cKey2 = wfRandomString();
 
+               $priorValue = null;
+               $priorAsOf = null;
                $wasSet = 0;
-               $func = function( $old, &$ttl ) use ( &$wasSet, $value ) {
+               $func = function( $old, &$ttl, &$opts, $asOf )
+                       use ( &$wasSet, &$priorValue, &$priorAsOf, $value )
+               {
                        ++$wasSet;
+                       $priorValue = $old;
+                       $priorAsOf = $asOf;
                        $ttl = 20; // override with another value
                        return $value;
                };
@@ -131,6 +178,8 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $v = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] + $extOpts );
                $this->assertEquals( $value, $v, "Value returned" );
                $this->assertEquals( 1, $wasSet, "Value regenerated" );
+               $this->assertFalse( $priorValue, "No prior value" );
+               $this->assertNull( $priorAsOf, "No prior value" );
 
                $curTTL = null;
                $cache->get( $key, $curTTL );
@@ -153,6 +202,8 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                );
                $this->assertEquals( $value, $v, "Value returned" );
                $this->assertEquals( 1, $wasSet, "Value regenerated due to check keys" );
+               $this->assertEquals( $value, $priorValue, "Has prior value" );
+               $this->assertType( 'float', $priorAsOf, "Has prior value" );
                $t1 = $cache->getCheckKeyTime( $cKey1 );
                $this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check keys generated on miss' );
                $t2 = $cache->getCheckKeyTime( $cKey2 );
@@ -734,6 +785,11 @@ class WANObjectCacheTest extends MediaWikiTestCase {
 
                $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
                $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
+
+               $ttl = $this->cache->adaptiveTTL( (string)$mtime, $maxTTL, $minTTL, $factor );
+
+               $this->assertGreaterThanOrEqual( $adaptiveTTL - $margin, $ttl );
+               $this->assertLessThanOrEqual( $adaptiveTTL + $margin, $ttl );
        }
 
        public static function provideAdaptiveTTL() {
index 91789c5..70c0ece 100644 (file)
@@ -135,8 +135,9 @@ class LinkRendererTest extends MediaWikiLangTestCase {
        }
 
        public function testGetLinkClasses() {
+               $wanCache = ObjectCache::getMainWANInstance();
                $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
-               $linkCache = new LinkCache( $titleFormatter );
+               $linkCache = new LinkCache( $titleFormatter, $wanCache );
                $foobarTitle = new TitleValue( NS_MAIN, 'FooBar' );
                $redirectTitle = new TitleValue( NS_MAIN, 'Redirect' );
                $userTitle = new TitleValue( NS_USER, 'Someuser' );
index ad84c20..097e413 100644 (file)
@@ -29,11 +29,6 @@ class NewParserTest extends MediaWikiTestCase {
        public $functionHooks = [];
        public $transparentHooks = [];
 
-       // Fuzz test
-       public $maxFuzzTestLength = 300;
-       public $fuzzSeed = 0;
-       public $memoryLimit = 50;
-
        /**
         * @var DjVuSupport
         */
@@ -837,144 +832,6 @@ class NewParserTest extends MediaWikiTestCase {
                $this->assertEquals( $result, $out, $desc );
        }
 
-       /**
-        * Run a fuzz test series
-        * Draw input from a set of test files
-        *
-        * @todo fixme Needs some work to not eat memory until the world explodes
-        *
-        * @group ParserFuzz
-        */
-       public function testFuzzTests() {
-               global $wgParserTestFiles;
-
-               $files = $wgParserTestFiles;
-
-               if ( $this->getCliArg( 'file' ) ) {
-                       $files = [ $this->getCliArg( 'file' ) ];
-               }
-
-               $dict = $this->getFuzzInput( $files );
-               $dictSize = strlen( $dict );
-               $logMaxLength = log( $this->maxFuzzTestLength );
-
-               ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
-
-               $user = new User;
-               $opts = ParserOptions::newFromUser( $user );
-               $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
-
-               $id = 1;
-
-               while ( true ) {
-
-                       // Generate test input
-                       mt_srand( ++$this->fuzzSeed );
-                       $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
-                       $input = '';
-
-                       while ( strlen( $input ) < $totalLength ) {
-                               $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
-                               $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
-                               $offset = mt_rand( 0, $dictSize - $hairLength );
-                               $input .= substr( $dict, $offset, $hairLength );
-                       }
-
-                       $this->setupGlobals();
-                       $parser = $this->getParser();
-
-                       // Run the test
-                       try {
-                               $parser->parse( $input, $title, $opts );
-                               $this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" );
-                       } catch ( Exception $exception ) {
-                               $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input );
-
-                               $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\n" .
-                                       "Input: $input_dump\n\nError: {$exception->getMessage()}\n\n" .
-                                       "Backtrace: {$exception->getTraceAsString()}" );
-                       }
-
-                       $this->teardownGlobals();
-                       $parser->__destruct();
-
-                       if ( $id % 100 == 0 ) {
-                               $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
-                               // echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
-                               if ( $usage > 90 ) {
-                                       $ret = "Out of memory:\n";
-                                       $memStats = $this->getMemoryBreakdown();
-
-                                       foreach ( $memStats as $name => $usage ) {
-                                               $ret .= "$name: $usage\n";
-                                       }
-
-                                       throw new MWException( $ret );
-                               }
-                       }
-
-                       $id++;
-               }
-       }
-
-       // Various getter functions
-
-       /**
-        * Get an input dictionary from a set of parser test files
-        * @param array $filenames
-        * @return string
-        */
-       function getFuzzInput( $filenames ) {
-               $dict = '';
-
-               foreach ( $filenames as $filename ) {
-                       $contents = file_get_contents( $filename );
-                       preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches );
-
-                       foreach ( $matches[1] as $match ) {
-                               $dict .= $match . "\n";
-                       }
-               }
-
-               return $dict;
-       }
-
-       /**
-        * Get a memory usage breakdown
-        * @return array
-        */
-       function getMemoryBreakdown() {
-               $memStats = [];
-
-               foreach ( $GLOBALS as $name => $value ) {
-                       $memStats['$' . $name] = strlen( serialize( $value ) );
-               }
-
-               $classes = get_declared_classes();
-
-               foreach ( $classes as $class ) {
-                       $rc = new ReflectionClass( $class );
-                       $props = $rc->getStaticProperties();
-                       $memStats[$class] = strlen( serialize( $props ) );
-                       $methods = $rc->getMethods();
-
-                       foreach ( $methods as $method ) {
-                               $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
-                       }
-               }
-
-               $functions = get_defined_functions();
-
-               foreach ( $functions['user'] as $function ) {
-                       $rf = new ReflectionFunction( $function );
-                       $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
-               }
-
-               asort( $memStats );
-
-               return $memStats;
-       }
-
        /**
         * Get a Parser object
         * @param Preprocessor $preprocessor
index 85834d7..404fd97 100644 (file)
@@ -114,25 +114,25 @@ class ResourceLoaderWikiModuleTest extends ResourceLoaderTestCase {
                        [ [], 'test1', true ],
                        // 'site' module with a non-empty page
                        [
-                               [ 'MediaWiki:Common.js' => [ 'rev_sha1' => 'dmh6qn', 'rev_len' => 1234 ] ],
+                               [ 'MediaWiki:Common.js' => [ 'page_len' => 1234 ] ],
                                'site',
                                false,
                        ],
                        // 'site' module with an empty page
                        [
-                               [ 'MediaWiki:Foo.js' => [ 'rev_sha1' => 'phoi', 'rev_len' => 0 ] ],
+                               [ 'MediaWiki:Foo.js' => [ 'page_len' => 0 ] ],
                                'site',
                                false,
                        ],
                        // 'user' module with a non-empty page
                        [
-                               [ 'User:Example/common.js' => [ 'rev_sha1' => 'j7ssba', 'rev_len' => 25 ] ],
+                               [ 'User:Example/common.js' => [ 'page_len' => 25 ] ],
                                'user',
                                false,
                        ],
                        // 'user' module with an empty page
                        [
-                               [ 'User:Example/foo.js' => [ 'rev_sha1' => 'phoi', 'rev_len' => 0 ] ],
+                               [ 'User:Example/foo.js' => [ 'page_len' => 0 ] ],
                                'user',
                                true,
                        ],
index 2de4bff..0ee4c13 100644 (file)
@@ -3,7 +3,7 @@
 /**
  * @covers FileContentsHasherTest
  */
-class FileContentsHasherTest extends MediaWikiTestCase {
+class FileContentsHasherTest extends PHPUnit_Framework_TestCase {
 
        public function provideSingleFile() {
                return array_map( function ( $file ) {
index 4c85c3d..905d14c 100644 (file)
@@ -4,7 +4,7 @@
  * @group Hash
  */
 
-class MWCryptHashTest extends MediaWikiTestCase {
+class MWCryptHashTest extends PHPUnit_Framework_TestCase {
 
        public function testHashLength() {
                if ( MWCryptHash::hashAlgo() !== 'whirlpool' ) {
index a70946a..4158863 100755 (executable)
@@ -10,8 +10,6 @@
 // through this entry point or not.
 define( 'MW_PHPUNIT_TEST', true );
 
-$wgPhpUnitClass = 'PHPUnit_TextUI_Command';
-
 // Start up MediaWiki in command-line mode
 require_once dirname( dirname( __DIR__ ) ) . "/maintenance/Maintenance.php";
 
@@ -27,6 +25,7 @@ class PHPUnitMaintClass extends Maintenance {
                'use-normal-tables' => false,
                'reuse-db' => false,
                'wiki' => false,
+               'profiler' => false,
        ];
 
        public function __construct() {
@@ -167,7 +166,7 @@ class PHPUnitMaintClass extends Maintenance {
 
                // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
                // But PHPUnit may not be loaded yet, so we have to wait until just
-               // before PHPUnit_TextUI_Command::main() is executed at the end of this file.
+               // before PHPUnit_TextUI_Command::main() is executed.
        }
 
        public function execute() {
@@ -188,9 +187,10 @@ class PHPUnitMaintClass extends Maintenance {
                                [ '--configuration', $IP . '/tests/phpunit/suite.xml' ] );
                }
 
+               $phpUnitClass = 'PHPUnit_TextUI_Command';
+
                if ( $this->hasOption( 'with-phpunitclass' ) ) {
-                       global $wgPhpUnitClass;
-                       $wgPhpUnitClass = $this->getOption( 'with-phpunitclass' );
+                       $phpUnitClass = $this->getOption( 'with-phpunitclass' );
 
                        # Cleanup $args array so the option and its value do not
                        # pollute PHPUnit
@@ -220,6 +220,25 @@ class PHPUnitMaintClass extends Maintenance {
                        }
                }
 
+               if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
+                       echo "PHPUnit not found. Please install it and other dev dependencies by
+               running `composer install` in MediaWiki root directory.\n";
+                       exit( 1 );
+               }
+               if ( !class_exists( $phpUnitClass ) ) {
+                       echo "PHPUnit entry point '" . $phpUnitClass . "' not found. Please make sure you installed
+               the containing component and check the spelling of the class name.\n";
+                       exit( 1 );
+               }
+
+               echo defined( 'HHVM_VERSION' ) ?
+                       'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
+                       'Using PHP ' . PHP_VERSION . "\n";
+
+               // Prepare global services for unit tests.
+               MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
+
+               $phpUnitClass::main();
        }
 
        public function getDbType() {
@@ -250,25 +269,3 @@ class PHPUnitMaintClass extends Maintenance {
 
 $maintClass = 'PHPUnitMaintClass';
 require RUN_MAINTENANCE_IF_MAIN;
-
-if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
-       echo "PHPUnit not found. Please install it and other dev dependencies by
-running `composer install` in MediaWiki root directory.\n";
-       exit( 1 );
-}
-if ( !class_exists( $wgPhpUnitClass ) ) {
-       echo "PHPUnit entry point '" . $wgPhpUnitClass . "' not found. Please make sure you installed
-the containing component and check the spelling of the class name.\n";
-       exit( 1 );
-}
-
-echo defined( 'HHVM_VERSION' ) ?
-       'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
-       'Using PHP ' . PHP_VERSION . "\n";
-
-// Prepare global services for unit tests.
-// FIXME: this should be done in the finalSetup() method,
-// but PHPUnit may not have been loaded at that point.
-MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
-
-$wgPhpUnitClass::main();
diff --git a/tests/testHelpers.inc b/tests/testHelpers.inc
deleted file mode 100644 (file)
index 1369406..0000000
+++ /dev/null
@@ -1,908 +0,0 @@
-<?php
-/**
- * Recording for passing/failing tests.
- *
- * 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 Testing
- */
-
-/**
- * Interface to record parser test results.
- *
- * The ITestRecorder is a very simple interface to record the result of
- * MediaWiki parser tests. One should call start() before running the
- * full parser tests and end() once all the tests have been finished.
- * After each test, you should use record() to keep track of your tests
- * results. Finally, report() is used to generate a summary of your
- * test run, one could dump it to the console for human consumption or
- * register the result in a database for tracking purposes.
- *
- * @since 1.22
- */
-interface ITestRecorder {
-
-       /**
-        * Called at beginning of the parser test run
-        */
-       public function start();
-
-       /**
-        * Called after each test
-        * @param string $test
-        * @param integer $subtest
-        * @param bool $result
-        */
-       public function record( $test, $subtest, $result );
-
-       /**
-        * Called before finishing the test run
-        */
-       public function report();
-
-       /**
-        * Called at the end of the parser test run
-        */
-       public function end();
-
-}
-
-class TestRecorder implements ITestRecorder {
-       public $parent;
-       public $term;
-
-       function __construct( $parent ) {
-               $this->parent = $parent;
-               $this->term = $parent->term;
-       }
-
-       function start() {
-               $this->total = 0;
-               $this->success = 0;
-       }
-
-       function record( $test, $subtest, $result ) {
-               $this->total++;
-               $this->success += ( $result ? 1 : 0 );
-       }
-
-       function end() {
-               // dummy
-       }
-
-       function report() {
-               if ( $this->total > 0 ) {
-                       $this->reportPercentage( $this->success, $this->total );
-               } else {
-                       throw new MWException( "No tests found.\n" );
-               }
-       }
-
-       function reportPercentage( $success, $total ) {
-               $ratio = wfPercent( 100 * $success / $total );
-               print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... ";
-
-               if ( $success == $total ) {
-                       print $this->term->color( 32 ) . "ALL TESTS PASSED!";
-               } else {
-                       $failed = $total - $success;
-                       print $this->term->color( 31 ) . "$failed tests failed!";
-               }
-
-               print $this->term->reset() . "\n";
-
-               return ( $success == $total );
-       }
-}
-
-class DbTestPreviewer extends TestRecorder {
-       protected $lb; // /< Database load balancer
-       protected $db; // /< Database connection to the main DB
-       protected $curRun; // /< run ID number for the current run
-       protected $prevRun; // /< run ID number for the previous run, if any
-       protected $results; // /< Result array
-
-       /**
-        * This should be called before the table prefix is changed
-        * @param TestRecorder $parent
-        */
-       function __construct( $parent ) {
-               parent::__construct( $parent );
-
-               $this->lb = wfGetLBFactory()->newMainLB();
-               // This connection will have the wiki's table prefix, not parsertest_
-               $this->db = $this->lb->getConnection( DB_MASTER );
-       }
-
-       /**
-        * Set up result recording; insert a record for the run with the date
-        * and all that fun stuff
-        */
-       function start() {
-               parent::start();
-
-               if ( !$this->db->tableExists( 'testrun', __METHOD__ )
-                       || !$this->db->tableExists( 'testitem', __METHOD__ )
-               ) {
-                       print "WARNING> `testrun` table not found in database.\n";
-                       $this->prevRun = false;
-               } else {
-                       // We'll make comparisons against the previous run later...
-                       $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' );
-               }
-
-               $this->results = [];
-       }
-
-       function getName( $test, $subtest ) {
-               if ( $subtest ) {
-                       return "$test subtest #$subtest";
-               } else {
-                       return $test;
-               }
-       }
-
-       function record( $test, $subtest, $result ) {
-               parent::record( $test, $subtest, $result );
-               $this->results[ $this->getName( $test, $subtest ) ] = $result;
-       }
-
-       function report() {
-               if ( $this->prevRun ) {
-                       // f = fail, p = pass, n = nonexistent
-                       // codes show before then after
-                       $table = [
-                               'fp' => 'previously failing test(s) now PASSING! :)',
-                               'pn' => 'previously PASSING test(s) removed o_O',
-                               'np' => 'new PASSING test(s) :)',
-
-                               'pf' => 'previously passing test(s) now FAILING! :(',
-                               'fn' => 'previously FAILING test(s) removed O_o',
-                               'nf' => 'new FAILING test(s) :(',
-                               'ff' => 'still FAILING test(s) :(',
-                       ];
-
-                       $prevResults = [];
-
-                       $res = $this->db->select( 'testitem', [ 'ti_name', 'ti_success' ],
-                               [ 'ti_run' => $this->prevRun ], __METHOD__ );
-
-                       foreach ( $res as $row ) {
-                               if ( !$this->parent->regex
-                                       || preg_match( "/{$this->parent->regex}/i", $row->ti_name )
-                               ) {
-                                       $prevResults[$row->ti_name] = $row->ti_success;
-                               }
-                       }
-
-                       $combined = array_keys( $this->results + $prevResults );
-
-                       # Determine breakdown by change type
-                       $breakdown = [];
-                       foreach ( $combined as $test ) {
-                               if ( !isset( $prevResults[$test] ) ) {
-                                       $before = 'n';
-                               } elseif ( $prevResults[$test] == 1 ) {
-                                       $before = 'p';
-                               } else /* if ( $prevResults[$test] == 0 )*/ {
-                                       $before = 'f';
-                               }
-
-                               if ( !isset( $this->results[$test] ) ) {
-                                       $after = 'n';
-                               } elseif ( $this->results[$test] == 1 ) {
-                                       $after = 'p';
-                               } else /*if ( $this->results[$test] == 0 ) */ {
-                                       $after = 'f';
-                               }
-
-                               $code = $before . $after;
-
-                               if ( isset( $table[$code] ) ) {
-                                       $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after );
-                               }
-                       }
-
-                       # Write out results
-                       foreach ( $table as $code => $label ) {
-                               if ( !empty( $breakdown[$code] ) ) {
-                                       $count = count( $breakdown[$code] );
-                                       printf( "\n%4d %s\n", $count, $label );
-
-                                       foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) {
-                                               print "      * $differing_test_name  [$statusInfo]\n";
-                                       }
-                               }
-                       }
-               } else {
-                       print "No previous test runs to compare against.\n";
-               }
-
-               print "\n";
-               parent::report();
-       }
-
-       /**
-        * Returns a string giving information about when a test last had a status change.
-        * Could help to track down when regressions were introduced, as distinct from tests
-        * which have never passed (which are more change requests than regressions).
-        * @param string $testname
-        * @param string $after
-        * @return string
-        */
-       private function getTestStatusInfo( $testname, $after ) {
-               // If we're looking at a test that has just been removed, then say when it first appeared.
-               if ( $after == 'n' ) {
-                       $changedRun = $this->db->selectField( 'testitem',
-                               'MIN(ti_run)',
-                               [ 'ti_name' => $testname ],
-                               __METHOD__ );
-                       $appear = $this->db->selectRow( 'testrun',
-                               [ 'tr_date', 'tr_mw_version' ],
-                               [ 'tr_id' => $changedRun ],
-                               __METHOD__ );
-
-                       return "First recorded appearance: "
-                               . date( "d-M-Y H:i:s", strtotime( $appear->tr_date ) )
-                               . ", " . $appear->tr_mw_version;
-               }
-
-               // Otherwise, this test has previous recorded results.
-               // See when this test last had a different result to what we're seeing now.
-               $conds = [
-                       'ti_name' => $testname,
-                       'ti_success' => ( $after == 'f' ? "1" : "0" ) ];
-
-               if ( $this->curRun ) {
-                       $conds[] = "ti_run != " . $this->db->addQuotes( $this->curRun );
-               }
-
-               $changedRun = $this->db->selectField( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ );
-
-               // If no record of ever having had a different result.
-               if ( is_null( $changedRun ) ) {
-                       if ( $after == "f" ) {
-                               return "Has never passed";
-                       } else {
-                               return "Has never failed";
-                       }
-               }
-
-               // Otherwise, we're looking at a test whose status has changed.
-               // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.)
-               // In this situation, give as much info as we can as to when it changed status.
-               $pre = $this->db->selectRow( 'testrun',
-                       [ 'tr_date', 'tr_mw_version' ],
-                       [ 'tr_id' => $changedRun ],
-                       __METHOD__ );
-               $post = $this->db->selectRow( 'testrun',
-                       [ 'tr_date', 'tr_mw_version' ],
-                       [ "tr_id > " . $this->db->addQuotes( $changedRun ) ],
-                       __METHOD__,
-                       [ "LIMIT" => 1, "ORDER BY" => 'tr_id' ]
-               );
-
-               if ( $post ) {
-                       $postDate = date( "d-M-Y H:i:s", strtotime( $post->tr_date ) ) . ", {$post->tr_mw_version}";
-               } else {
-                       $postDate = 'now';
-               }
-
-               return ( $after == "f" ? "Introduced" : "Fixed" ) . " between "
-                       . date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
-                       . " and $postDate";
-       }
-
-       /**
-        * Close the DB connection
-        */
-       function end() {
-               $this->lb->closeAll();
-               parent::end();
-       }
-}
-
-class DbTestRecorder extends DbTestPreviewer {
-       public $version;
-
-       /**
-        * Set up result recording; insert a record for the run with the date
-        * and all that fun stuff
-        */
-       function start() {
-               $this->db->begin( __METHOD__ );
-
-               if ( !$this->db->tableExists( 'testrun' )
-                       || !$this->db->tableExists( 'testitem' )
-               ) {
-                       print "WARNING> `testrun` table not found in database. Trying to create table.\n";
-                       $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
-                       echo "OK, resuming.\n";
-               }
-
-               parent::start();
-
-               $this->db->insert( 'testrun',
-                       [
-                               'tr_date' => $this->db->timestamp(),
-                               'tr_mw_version' => $this->version,
-                               'tr_php_version' => PHP_VERSION,
-                               'tr_db_version' => $this->db->getServerVersion(),
-                               'tr_uname' => php_uname()
-                       ],
-                       __METHOD__ );
-               if ( $this->db->getType() === 'postgres' ) {
-                       $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' );
-               } else {
-                       $this->curRun = $this->db->insertId();
-               }
-       }
-
-       /**
-        * Record an individual test item's success or failure to the db
-        *
-        * @param string $test
-        * @param bool $result
-        */
-       function record( $test, $subtest, $result ) {
-               parent::record( $test, $subtest, $result );
-
-               $this->db->insert( 'testitem',
-                       [
-                               'ti_run' => $this->curRun,
-                               'ti_name' => $this->getName( $test, $subtest ),
-                               'ti_success' => $result ? 1 : 0,
-                       ],
-                       __METHOD__ );
-       }
-
-       /**
-        * Commit transaction and clean up for result recording
-        */
-       function end() {
-               $this->db->commit( __METHOD__ );
-               parent::end();
-       }
-}
-
-class TestFileIterator implements Iterator {
-       private $file;
-       private $fh;
-       /**
-        * @var ParserTest|MediaWikiParserTest An instance of ParserTest (parserTests.php)
-        *  or MediaWikiParserTest (phpunit)
-        */
-       private $parserTest;
-       private $index = 0;
-       private $test;
-       private $section = null;
-       /** String|null: current test section being analyzed */
-       private $sectionData = [];
-       private $lineNum;
-       private $eof;
-       # Create a fake parser tests which never run anything unless
-       # asked to do so. This will avoid running hooks for a disabled test
-       private $delayedParserTest;
-       private $nextSubTest = 0;
-
-       function __construct( $file, $parserTest ) {
-               $this->file = $file;
-               $this->fh = fopen( $this->file, "rt" );
-
-               if ( !$this->fh ) {
-                       throw new MWException( "Couldn't open file '$file'\n" );
-               }
-
-               $this->parserTest = $parserTest;
-               $this->delayedParserTest = new DelayedParserTest();
-
-               $this->lineNum = $this->index = 0;
-       }
-
-       function rewind() {
-               if ( fseek( $this->fh, 0 ) ) {
-                       throw new MWException( "Couldn't fseek to the start of '$this->file'\n" );
-               }
-
-               $this->index = -1;
-               $this->lineNum = 0;
-               $this->eof = false;
-               $this->next();
-
-               return true;
-       }
-
-       function current() {
-               return $this->test;
-       }
-
-       function key() {
-               return $this->index;
-       }
-
-       function next() {
-               if ( $this->readNextTest() ) {
-                       $this->index++;
-                       return true;
-               } else {
-                       $this->eof = true;
-               }
-       }
-
-       function valid() {
-               return $this->eof != true;
-       }
-
-       function setupCurrentTest() {
-               // "input" and "result" are old section names allowed
-               // for backwards-compatibility.
-               $input = $this->checkSection( [ 'wikitext', 'input' ], false );
-               $result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
-               // some tests have "with tidy" and "without tidy" variants
-               $tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
-               if ( $tidy != false ) {
-                       if ( $this->nextSubTest == 0 ) {
-                               if ( $result != false ) {
-                                       $this->nextSubTest = 1; // rerun non-tidy variant later
-                               }
-                               $result = $tidy;
-                       } else {
-                               $this->nextSubTest = 0; // go on to next test after this
-                               $tidy = false;
-                       }
-               }
-
-               if ( !isset( $this->sectionData['options'] ) ) {
-                       $this->sectionData['options'] = '';
-               }
-
-               if ( !isset( $this->sectionData['config'] ) ) {
-                       $this->sectionData['config'] = '';
-               }
-
-               $isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
-                       !$this->parserTest->runDisabled;
-               $isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
-                       $result == 'html' &&
-                       !$this->parserTest->runParsoid;
-               $isFiltered = !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] );
-               if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
-                       # disabled test
-                       return false;
-               }
-
-               # We are really going to run the test, run pending hooks and hooks function
-               wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" );
-               $hooksResult = $this->delayedParserTest->unleash( $this->parserTest );
-               if ( !$hooksResult ) {
-                       # Some hook reported an issue. Abort.
-                       throw new MWException( "Problem running requested parser hook from the test file" );
-               }
-
-               $this->test = [
-                       'test' => ParserTest::chomp( $this->sectionData['test'] ),
-                       'subtest' => $this->nextSubTest,
-                       'input' => ParserTest::chomp( $this->sectionData[$input] ),
-                       'result' => ParserTest::chomp( $this->sectionData[$result] ),
-                       'options' => ParserTest::chomp( $this->sectionData['options'] ),
-                       'config' => ParserTest::chomp( $this->sectionData['config'] ),
-               ];
-               if ( $tidy != false ) {
-                       $this->test['options'] .= " tidy";
-               }
-               return true;
-       }
-
-       function readNextTest() {
-               # Run additional subtests of previous test
-               while ( $this->nextSubTest > 0 ) {
-                       if ( $this->setupCurrentTest() ) {
-                               return true;
-                       }
-               }
-
-               $this->clearSection();
-               # Reset hooks for the delayed test object
-               $this->delayedParserTest->reset();
-
-               while ( false !== ( $line = fgets( $this->fh ) ) ) {
-                       $this->lineNum++;
-                       $matches = [];
-
-                       if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
-                               $this->section = strtolower( $matches[1] );
-
-                               if ( $this->section == 'endarticle' ) {
-                                       $this->checkSection( 'text' );
-                                       $this->checkSection( 'article' );
-
-                                       $this->parserTest->addArticle(
-                                               ParserTest::chomp( $this->sectionData['article'] ),
-                                               $this->sectionData['text'], $this->lineNum );
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'endhooks' ) {
-                                       $this->checkSection( 'hooks' );
-
-                                       foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
-                                               $line = trim( $line );
-
-                                               if ( $line ) {
-                                                       $this->delayedParserTest->requireHook( $line );
-                                               }
-                                       }
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'endfunctionhooks' ) {
-                                       $this->checkSection( 'functionhooks' );
-
-                                       foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
-                                               $line = trim( $line );
-
-                                               if ( $line ) {
-                                                       $this->delayedParserTest->requireFunctionHook( $line );
-                                               }
-                                       }
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'endtransparenthooks' ) {
-                                       $this->checkSection( 'transparenthooks' );
-
-                                       foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
-                                               $line = trim( $line );
-
-                                               if ( $line ) {
-                                                       $this->delayedParserTest->requireTransparentHook( $line );
-                                               }
-                                       }
-
-                                       $this->clearSection();
-
-                                       continue;
-                               }
-
-                               if ( $this->section == 'end' ) {
-                                       $this->checkSection( 'test' );
-                                       do {
-                                               if ( $this->setupCurrentTest() ) {
-                                                       return true;
-                                               }
-                                       } while ( $this->nextSubTest > 0 );
-                                       # go on to next test (since this was disabled)
-                                       $this->clearSection();
-                                       $this->delayedParserTest->reset();
-                                       continue;
-                               }
-
-                               if ( isset( $this->sectionData[$this->section] ) ) {
-                                       throw new MWException( "duplicate section '$this->section' "
-                                               . "at line {$this->lineNum} of $this->file\n" );
-                               }
-
-                               $this->sectionData[$this->section] = '';
-
-                               continue;
-                       }
-
-                       if ( $this->section ) {
-                               $this->sectionData[$this->section] .= $line;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Clear section name and its data
-        */
-       private function clearSection() {
-               $this->sectionData = [];
-               $this->section = null;
-
-       }
-
-       /**
-        * Verify the current section data has some value for the given token
-        * name(s) (first parameter).
-        * Throw an exception if it is not set, referencing current section
-        * and adding the current file name and line number
-        *
-        * @param string|array $tokens Expected token(s) that should have been
-        * mentioned before closing this section
-        * @param bool $fatal True iff an exception should be thrown if
-        * the section is not found.
-        * @return bool|string
-        * @throws MWException
-        */
-       private function checkSection( $tokens, $fatal = true ) {
-               if ( is_null( $this->section ) ) {
-                       throw new MWException( __METHOD__ . " can not verify a null section!\n" );
-               }
-               if ( !is_array( $tokens ) ) {
-                       $tokens = [ $tokens ];
-               }
-               if ( count( $tokens ) == 0 ) {
-                       throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
-               }
-
-               $data = $this->sectionData;
-               $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
-                       return isset( $data[$token] );
-               } );
-
-               if ( count( $tokens ) == 0 ) {
-                       if ( !$fatal ) {
-                               return false;
-                       }
-                       throw new MWException( sprintf(
-                               "'%s' without '%s' at line %s of %s\n",
-                               $this->section,
-                               implode( ',', $tokens ),
-                               $this->lineNum,
-                               $this->file
-                       ) );
-               }
-               if ( count( $tokens ) > 1 ) {
-                       throw new MWException( sprintf(
-                               "'%s' with unexpected tokens '%s' at line %s of %s\n",
-                               $this->section,
-                               implode( ',', $tokens ),
-                               $this->lineNum,
-                               $this->file
-                       ) );
-               }
-
-               return array_values( $tokens )[0];
-       }
-}
-
-/**
- * An iterator for use as a phpunit data provider. Provides the test arguments
- * in the order expected by NewParserTest::testParserTest().
- */
-class TestFileDataProvider extends TestFileIterator {
-       function current() {
-               $test = parent::current();
-               if ( $test ) {
-                       return [
-                               $test['test'],
-                               $test['input'],
-                               $test['result'],
-                               $test['options'],
-                               $test['config'],
-                       ];
-               } else {
-                       return $test;
-               }
-       }
-}
-
-/**
- * A class to delay execution of a parser test hooks.
- */
-class DelayedParserTest {
-
-       /** Initialized on construction */
-       private $hooks;
-       private $fnHooks;
-       private $transparentHooks;
-
-       public function __construct() {
-               $this->reset();
-       }
-
-       /**
-        * Init/reset or forgot about the current delayed test.
-        * Call to this will erase any hooks function that were pending.
-        */
-       public function reset() {
-               $this->hooks = [];
-               $this->fnHooks = [];
-               $this->transparentHooks = [];
-       }
-
-       /**
-        * Called whenever we actually want to run the hook.
-        * Should be the case if we found the parserTest is not disabled
-        * @param ParserTest|NewParserTest $parserTest
-        * @return bool
-        * @throws MWException
-        */
-       public function unleash( &$parserTest ) {
-               if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) {
-                       throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or "
-                               . "NewParserTest classes\n" );
-               }
-
-               # Trigger delayed hooks. Any failure will make us abort
-               foreach ( $this->hooks as $hook ) {
-                       $ret = $parserTest->requireHook( $hook );
-                       if ( !$ret ) {
-                               return false;
-                       }
-               }
-
-               # Trigger delayed function hooks. Any failure will make us abort
-               foreach ( $this->fnHooks as $fnHook ) {
-                       $ret = $parserTest->requireFunctionHook( $fnHook );
-                       if ( !$ret ) {
-                               return false;
-                       }
-               }
-
-               # Trigger delayed transparent hooks. Any failure will make us abort
-               foreach ( $this->transparentHooks as $hook ) {
-                       $ret = $parserTest->requireTransparentHook( $hook );
-                       if ( !$ret ) {
-                               return false;
-                       }
-               }
-
-               # Delayed execution was successful.
-               return true;
-       }
-
-       /**
-        * Similar to ParserTest object but does not run anything
-        * Use unleash() to really execute the hook
-        * @param string $hook
-        */
-       public function requireHook( $hook ) {
-               $this->hooks[] = $hook;
-       }
-
-       /**
-        * Similar to ParserTest object but does not run anything
-        * Use unleash() to really execute the hook function
-        * @param string $fnHook
-        */
-       public function requireFunctionHook( $fnHook ) {
-               $this->fnHooks[] = $fnHook;
-       }
-
-       /**
-        * Similar to ParserTest object but does not run anything
-        * Use unleash() to really execute the hook function
-        * @param string $hook
-        */
-       public function requireTransparentHook( $hook ) {
-               $this->transparentHooks[] = $hook;
-       }
-
-}
-
-/**
- * Initialize and detect the DjVu files support
- */
-class DjVuSupport {
-
-       /**
-        * Initialises DjVu tools global with default values
-        */
-       public function __construct() {
-               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgFileExtensions, $wgDjvuTxt;
-
-               $wgDjvuRenderer = $wgDjvuRenderer ? $wgDjvuRenderer : '/usr/bin/ddjvu';
-               $wgDjvuDump = $wgDjvuDump ? $wgDjvuDump : '/usr/bin/djvudump';
-               $wgDjvuToXML = $wgDjvuToXML ? $wgDjvuToXML : '/usr/bin/djvutoxml';
-               $wgDjvuTxt = $wgDjvuTxt ? $wgDjvuTxt : '/usr/bin/djvutxt';
-
-               if ( !in_array( 'djvu', $wgFileExtensions ) ) {
-                       $wgFileExtensions[] = 'djvu';
-               }
-       }
-
-       /**
-        * Returns true if the DjVu tools are usable
-        *
-        * @return bool
-        */
-       public function isEnabled() {
-               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgDjvuTxt;
-
-               return is_executable( $wgDjvuRenderer )
-                       && is_executable( $wgDjvuDump )
-                       && is_executable( $wgDjvuToXML )
-                       && is_executable( $wgDjvuTxt );
-       }
-}
-
-/**
- * Initialize and detect the tidy support
- */
-class TidySupport {
-       private $enabled;
-       private $config;
-
-       /**
-        * Determine if there is a usable tidy.
-        */
-       public function __construct( $useConfiguration = false ) {
-               global $IP, $wgUseTidy, $wgTidyBin, $wgTidyInternal, $wgTidyConfig,
-                       $wgTidyConf, $wgTidyOpts;
-
-               $this->enabled = true;
-               if ( $useConfiguration ) {
-                       if ( $wgTidyConfig !== null ) {
-                               $this->config = $wgTidyConfig;
-                       } elseif ( $wgUseTidy ) {
-                               $this->config = [
-                                       'tidyConfigFile' => $wgTidyConf,
-                                       'debugComment' => false,
-                                       'tidyBin' => $wgTidyBin,
-                                       'tidyCommandLine' => $wgTidyOpts
-                               ];
-                               if ( $wgTidyInternal ) {
-                                       $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
-                               } else {
-                                       $this->config['driver'] = 'RaggettExternal';
-                               }
-                       } else {
-                               $this->enabled = false;
-                       }
-               } else {
-                       $this->config = [
-                               'tidyConfigFile' => "$IP/includes/tidy/tidy.conf",
-                               'tidyCommandLine' => '',
-                       ];
-                       if ( extension_loaded( 'tidy' ) && class_exists( 'tidy' ) ) {
-                               $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
-                       } else {
-                               if ( is_executable( $wgTidyBin ) ) {
-                                       $this->config['driver'] = 'RaggettExternal';
-                                       $this->config['tidyBin'] = $wgTidyBin;
-                               } else {
-                                       $path = Installer::locateExecutableInDefaultPaths( $wgTidyBin );
-                                       if ( $path !== false ) {
-                                               $this->config['driver'] = 'RaggettExternal';
-                                               $this->config['tidyBin'] = $wgTidyBin;
-                                       } else {
-                                               $this->enabled = false;
-                                       }
-                               }
-                       }
-               }
-               if ( !$this->enabled ) {
-                       $this->config = [ 'driver' => 'disabled' ];
-               }
-       }
-
-       /**
-        * Returns true if tidy is usable
-        *
-        * @return bool
-        */
-       public function isEnabled() {
-               return $this->enabled;
-       }
-
-       public function getConfig() {
-               return $this->config;
-       }
-}